Preamble: running this code

There’s a lot of packages being loaded here! If you don’t have any of these already and want to run this code, then you’ll need these. If you want to embed the interactive LDA visualizations using the custom functions discussed below, you’ll also need to get the development version of LDAvis even if you downloaded the regular CRAN version earlier. The development version fixes an issue with the topics being numbered differently in the visualization vs in R. If you want to run the BERTopic model in Python then you’ll also need to install pandas, nltk, and bertopic.

Trying to compile this entire RMD is probably going to be very slow and maybe won’t work at all. So instead of doing that, I would recommend you just take out the chunks you really want to use and run them interactively. In general, the dictionary based stuff is going to run much faster than the topic models.

#### note that eval = false for this chunk - only run it if you need it! 

#### additional quanteda packages: 
install.packages('quanteda.textmodels')
install.packages('quanteda.textplots')

#### development version of LDAvis - fixes a problem with topic ordering
devtools::install_github("cpsievert/LDAvis")
####additional dictionaries: 

devtools::install_github("kbenoit/quanteda.dictionaries") 
remotes::install_github("quanteda/quanteda.sentiment")
###Seeded LDA 

install.packages("seededlda")
### Structural topic model 
install.packages("stm")
### additional package for viewing STM output: 
install.packages("stminsights")

###Keyword LDA
install.packages("KeyATM")

Setup: cleaning the data

Since most of these models are going to use the “bag-of-words” assumption, we can do the same cleaning steps for each. The code below should be familiar to you already, and draws from a corpus of news stories scraped from CNN AND Fox News from 2020 through September 2023. The “text” column contains the text, and the “source” column contains the source. There are also dates associated with each article.

library(tidyverse)  # for data manipulation
library(quanteda)  # for cleaning and analysis of text
library(quanteda.textstats) # extra quanteda commands
library(quanteda.textplots) # for plotting the keyness statistic


poldf<-read_csv('https://github.com/Neilblund/APAN/raw/main/news_sample.csv')

# normalizing some apostrophes and quotes (surprisingly important!)
poldf$text<-str_replace_all(poldf$text,"\u201C|\u201D", '"')%>%
  str_replace_all(.,"\u2019|\u2018", "'")
# convert to a corpus
pol_corpus<-poldf%>%
  with(.,corpus(text, 
                docvars = data.frame(url, headline,  date, source)))

# tokenize the texts
pol_tokens<-tokens(pol_corpus, 
                   what = 'word',
                   # this keeps characteristics about the documents like the headline and url
                   remove_punct =TRUE,
                   remove_symbols = TRUE,
                   remove_numbers = TRUE,
                   remove_url = TRUE,
                   remove_separators = TRUE,
                   include_docvars = TRUE
                   )
# remove stop words 
toks_nostop <- tokens_select(pol_tokens, pattern = stopwords("en"), selection = "remove")
# convert to a document-feature matrix
pol_dfm<- dfm(toks_nostop)
# stem and then unstem words (using custom function in preamble to this document)
pol_dfm<-stem_unstem(pol_dfm) 
# if you don't want to use the stem_unstem function, run this instead:  
# pol_dfm<-dfm_wordstem(dfm)  

# remove features that occur less than 10 times
pol_dfm<-dfm_trim(pol_dfm, min_docfreq= 10)
# remove features that occur in more than 10% of documents 
# increasing max_docfreq may improve results, but it will take longer for models to converge
pol_dfm<- dfm_trim(pol_dfm, max_docfreq = 0.1, docfreq_type = "prop")

Keywords in context

KWIC stands for “keywords in context”. We can use KWIC to find stories that contain a term, and get the text surrounding that word. This code would pull all documents containing terms that started with “immig” (so: immigrant, immigration, immigrants etc.) along with a window of 3 words on either side.

(note that this is actually pulling from the pol_tokens object rather than the DFM, this approach doesn’t really require any cleaning to use)

# keywords in context
kw_immig <- kwic(pol_tokens, pattern =  "immig*", window=3)
# view the first 10
head(kw_immig, 5)

For your own work, you may want to get more context by increasing the window argument in the KWIC command. You might also consider writing these results to a csv file so you can read them in a spreadsheet. KWIC doesn’t automatically provide you with nice looking plots, but it can be a very good way to do things like develop a dictionary or get a general qualitative sense of how sources are discussing something.

Keyness

Keyness is a measure of relative frequency that can be useful for finding terms that are more common in particular subsets of documents. For instance, if we want to find terms that are more common in Fox News articles compared to CNN, we could just run the following:

tstat_key <- textstat_keyness(pol_dfm, 
                              # find distinctive terms for Fox news compared to CNN :
                              target = docvars(pol_dfm)$source == "Fox News")
textplot_keyness(tstat_key)

We could also focus on a subset of articles to see how sources differ in their coverage of similar topics. Here, we’re looking at keywords that are strongly associated with documents that mention Hunter Biden. The results suggest that Fox and CNN have some very distinct terminology when discussing this issue:

hunter_dfm<-dfm_subset(pol_dfm, 
                       #  get a subset of articles that mention Hunter Biden
                       subset  = str_detect(pol_corpus, "Hunter Biden"))
tstat_key <- textstat_keyness(hunter_dfm, 
                              
                              target = docvars(hunter_dfm)$source == "Fox News")
textplot_keyness(tstat_key)

Dictionary Methods

We can use the dictionary approach to simply match terms against a dictionary of terms we’re interested in. The policy agendas dictionary contains a list of terms associated with major policy areas. Here, we apply the agendas codebook to the tokens list, which removes all terms that aren’t part of the dictionary.

# download the policy agendas codebook (discussed here #https://www.almendron.com/tribuna/wp-content/uploads/2017/05/CAP2013v2.pdf)
policyAgendas<-readRDS(url('https://github.com/Neilblund/APAN/raw/main/policy_agendas_dictionary.rds'))

immig_tokens<-tokens_subset(pol_tokens, 
                       #  get a subset of articles that mention Hunter Biden
                       subset  = str_detect(pol_corpus, "immigr*"))

# convert to a quanteda dictionary format
policyagendas.dict <- dictionary(policyAgendas)

# apply the dictionary
agenda_politics <- tokens_lookup(pol_tokens, dictionary = policyagendas.dict, levels = 1)

# check out the first result: 
agenda_politics[[1]]
##  [1] "healthcare"            "healthcare"            "healthcare"           
##  [4] "finance"               "healthcare"            "healthcare"           
##  [7] "healthcare"            "healthcare"            "healthcare"           
## [10] "healthcare"            "intl_affairs"          "healthcare"           
## [13] "healthcare"            "crime"                 "healthcare"           
## [16] "healthcare"            "healthcare"            "healthcare"           
## [19] "healthcare"            "healthcare"            "healthcare"           
## [22] "healthcare"            "healthcare"            "healthcare"           
## [25] "healthcare"            "land-water-management"

We can convert this list of tokens to a document feature matrix. A DFM will have one row per document and one column per feature, the cells will contain counts of the number of times a given feature occurs in each document. In this case, our DFM will have one column for each of the topics in the policy agendas dictionary. We can use this to do things like count the number of documents which are about some topic.

# convert to a dfm
agenda_dfm<-dfm(agenda_politics)
# look at the first few rows
head(agenda_dfm, n=5)
## Document-feature matrix of: 5 documents, 28 features (82.86% sparse) and 4 docvars.
##        features
## docs    macroeconomics civil_rights healthcare agriculture forestry labour
##   text1              0            0         22           0        0      0
##   text2              0            0         10           0        0      0
##   text3              0            0          3           0        0      1
##   text4              1            0          1           0        0      1
##   text5              0            0          1           0        0      0
##        features
## docs    immigration education environment energy
##   text1           0         0           0      0
##   text2           0         0           0      0
##   text3           0         0           0      0
##   text4           0         0           0      0
##   text5           0         0           0      0
## [ reached max_nfeat ... 18 more features ]

textstat_frequency gives us some basic stats about our document features. The “frequency” column is the number of times a policy agenda topic occurs. The “docfreq” column is the number of documents which contain that agenda topic (regardless of whether it occurs multiple times in a single document). From this, we can see that “healthcare” is the most commonly occurring agenda topic.

agenda_freq<-textstat_frequency(agenda_dfm)
head(agenda_freq)

Put them all in a horizontal barplot:

agenda_freq%>%
  ggplot(aes(x = reorder(feature, docfreq), y=docfreq)) + 
  geom_bar(stat='identity', fill='lightblue') +
  xlab('policy agenda') + 
  ylab('number of documents mentioning') +
  coord_flip() +
  # using the minimal theme: 
  theme_minimal() +
  ggtitle('Document frequency of policy agenda terms in CNN politics articles')

Additional dictionaries/commands

Quanteda does have some additional dictionaries that may be of use for some of you. These can be accessed through the Quanteda.dictionaries package and the quanteda.sentiment package. Although these don’t appear to be on CRAN yet, you can install them by running the command below, and then loading them the way you would load any other library. The associated github page has information on the included dictionary files.

devtools::install_github("kbenoit/quanteda.dictionaries") 
remotes::install_github("quanteda/quanteda.sentiment")

The textstat_polarity function will automatically apply a dictionary and measure sentence polarity by getting the log of positive/negative terms in the text. The textstat_valence function can be used to compute scores for dictionaries that have multiple categories rather than just positive vs. negative.

Here’s an example of how you might use the textstat_polarity command from this package along with the Lexicoder Sentiment Dictionary. In this example, we’re measuring the document polarity, then graphing the average sentiment for documents for each source and depending on whether they mention immigration. The results suggest that Fox News tends to give more negative coverage when discussing immigration compared to their coverage of other topics, where as CNN covers immigration with a slightly more positive valence.

library(quanteda.sentiment)
# load the lexicoder sentiment dictionary
lsd<-quanteda.sentiment::data_dictionary_LSD2015

polarity<-textstat_polarity(pol_corpus, dictionary=lsd)%>%
  # add polarity to existing document variables
  bind_cols(docvars(pol_corpus))%>%
  # also add a column for mentions of immigration
  mutate(mentions_immigration = str_detect(pol_corpus,"immigr*"))

# plot polarity for all four groups
ggplot(polarity, aes(x=source, y=sentiment, fill=mentions_immigration)) + 
  geom_boxplot(notch=TRUE) +
  theme_minimal()

# test as an interaction term:
## summary(lm(sentiment ~ source * mentions_immigration, data=polarity))

Making a custom dictionary

Finally, keep in mind that you can create a dictionary from a named list. So if you want to create your own for a specific domain or language, or extend an existing one, the syntax should be reasonably straightforward.

custom_list<-list(COVID  =c('covid','coronavirus', 'vaccin*', 'lock down'),
                  Jan6 = c( "jan* 6*", "capitol", 'insurrection*'))



custom_dict <- dictionary(custom_list)
tok<-tokens_lookup(pol_tokens, dictionary=custom_dict)


table(unlist(tok))

Any of these approaches are a good starting point and I think you can definitely use them for your analyses, but you should be willing to admit their limitations when applied outside the area they were designed for. Check out this paper for some discussion of the limitations of dictionary based approaches.

Topic Modeling

LDA

Latent Dirichlet Allocation (LDA) also known as topic modeling is widely used approach for unsupervised document classification. “Unsupervised” in this case, means that you don’t need any prior knowledge about the corpus in order to make use of it. Instead, an LDA model will attempt to model documents as as a probability distribution over topics, and topics as a probability distribution over words. This is done automatically - all you really need to do is clean the text and choose a number of topics. That said, this also means that the results can be somewhat hard to interpret: you’ll have to make an educated guess about what topics you’re finding by looking at words that occur with a higher frequency in a given topic.

Note: If you’re looking for a quick mostly non-technical primer on this, I think Edwin Chen’s blog post is a good starting point, but there’s a lot of information about these online. If you’re looking for a deeper dive, here’s a good review article on the method and recent innovations.

Here’s an example of creating an LDA model with just 15 topics.

(If you want replicable result, make sure to run set.seed([some number]) right before you train the topic model. While topic models will theoretically converge toward a single “correct” answer, the numbering of the topics is completely arbitrary in standard LDA, so topic 10 might be topic 2 next time you run it.)

library(seededlda) # package for both seeded LDA and regular LDA
library(LDAvis) # for visualization 
# number of topics: 
K<-15

# the initialization is random, so set the random number seed for replicability 
set.seed(100)
lda_model<-textmodel_lda(pol_dfm, k = K, 
              max_iter=2000, 
              auto_iter = T,
              
              # raising this result in more uniform theta
              alpha = 0.1,
              #raising this will result in a more uniform phi
              beta = 0.01
              
              )

If you view lda_model$phi you should see a matrix with 15 rows (one row per topic) and around 8000 columns (one column per term). The number in each row represent the probability of that term for the topic. (you’ll sometimes see people refer to this matrix using the Greek letter \(\phi\) or as the “topic-word distribution”)

You can see the highest probability words in each topic using the command below, you’ll probably be able to pick out some concepts pretty quickly

#top 10 terms per topic
terms(lda_model, 10)%>%
  #converting to data frame (for prettier formatting)
  data.frame()

Meanwhile, the lda_model$theta represents the topic-word distribution matrix. If you view this object you should see one column per topic and one row per document. The numbers indicate the probability of a topic in a given document. (you’ll often see this distribution denoted as \(\theta\) or as the “document-topic matrix”)

# get the document-topic distributions for the first few articles: 
head(lda_model$theta)%>%
  data.frame()

One use of the the document topic distribution is for information retrieval. Based on the most likely terms, topic 13 appears to be picking up on stories related to the Supreme Court’s decision overturning Roe v. Wade. So documents with a high % of this topic should probably be about that story. And based on the urls, this intuition seems correct:

    #get document data
dobbs_docs<-docvars(pol_dfm)%>%
  
  # add % of topic 13 to document data
  mutate(dobbs = lda_model$theta[,4])%>%
  # sort from lowest to highest topic %
  arrange(-dobbs)


dobbs_docs%>%
  select(url)%>%
  slice_head(n=10)

We can use this insight to get some sense of how the volume of coverage on this topic has changed over time. Here, we’re getting the monthly average % of this topic within each document. The dobbs_timeline object just allows us to add a set of annotations to the timeline.

# make a timeline of events (so we can add annotations to the timeline)
dobbs_timeline<-data.frame(event= c('Ginsburg death',
                                    'Cert granted',
                                    'Oral arguments',
                                    'Dobbs leak', 
                                    'Dobbs opinion'),
                date = as.Date(c("2020-09-18",
                                 '2021-05-17',
                                 '2021-12-01',
                                 '2022-05-02', 
                                 '2022-06-24'))
                  
                  )%>%
  
  # convert timeline to months (to allow joining with monthly coverage data)
  mutate(month = floor_date(date, unit='months'))

monthly<-dobbs_docs%>%
  # round down to the nearest month
  mutate(month = floor_date(date, unit='months')
         )%>%
  
  # group by month and compute the monthly average: 
  group_by(month)%>%
  summarise(coverage = mean(dobbs)
            )%>%
  # join with timeline data
  left_join(dobbs_timeline)

And from here, we’ll create the plot in GGplot:

# create plot: 
monthly%>%
  ggplot(aes(x=month, y=coverage, label=event)) + 
  # create the line plot
  geom_line() +
  # add points for each month
  geom_point() +
  # turn off clipping (avoids removing labels if they're outside the plot area)
  coord_cartesian(clip = "off") + 
  
  # add labels (vjust -1 sets annotation just above the timeline)
  geom_label(vjust=-1) +
  ylab('Average topic %') +
  # using the minimal theme: 
  theme_minimal() +
  ggtitle('Average percent topic 13 (Roe overturned)')  +
  # show ticks every 3 months
  scale_x_date(breaks="3 month", date_labels = "%Ob '%y") +
  # labels at 45 degree angle
  theme(axis.text.x = element_text(angle = 45))

Lastly, you can use the LDAvis package to visualize and interpret your topics.

The left panel uses a principal component analysis to model the “distance” between each topic. Topics that are further apart are less similar to one another, and larger bubbles indicate the topic is more common. Selecting a topic will cause the right panel to display relevant terms for that topic. Note: clicking the links in the bottom right will bring up more detailed information on what these metrics mean, but the short version is that they’re both meant to make it easier to identify the most distinctive terms for each topic, rather than the terms that have the highest frequency.

In order to (hopefully) simplify the process of setup, there’s a custom function included in the header of this notebook that will allow you to save and embed these visualizations. Here’s the code you would use to make this display on your local device:

# a filename to save the plot under
modelvis<-'lda_vis.html'
# create the visualization
LDAvis_standalone(lda_model, outputfile=modelvis)
# bring up a window with the visualization: 
browseURL(modelvis)

You could take screenshots of this visualization for different topics in order to share it, but if you wanted to share the actual interactive version of this visualization in an Rmarkdown file or notebook, you could do it using the includeHTML function from the htmltools package.

lda_vis<-'lda_visual.html'
LDAvis_standalone(lda_model, outputfile=lda_vis)
htmltools::includeHTML(lda_vis)

Seeded LDA

In seeded LDA, you supply some initial terms and topics, and the model will infer additional terms based on your words.

Here I’ve created a custom dictionary with 10 topics and just a handful of terms for each. Notice that terms with * will be able to match different endings, so vaccin* should be able to match any of vaccinate, vaccine, and vaccination.

news_dict<-list('covid' = c('covid*', 'vaccin*', 'lockdown*'),
                'natsec' = c('afghan*', 'taliban', 'putin', 'china', 'ukrain*','zelensky', 'terror*'),

                'elections' = c('election', 'caucus', 'primary', 'debate*', 'nomination', 'midterm', 'vote*', 'poll*'),
                'trump' = c('indictment', 'impeachment', 'mar-a-lago'),
                'jan6' = c("capitol", 'january', 'chansley','fraud'),
                'blm'  = c('floyd' , 'black', 'police', "protest*"),
                'immigration' = c('immigrat*',  'border', 'homeland', 'migrant*'),
                'climate' = c('global','climate*','fuel', 'carbon', 'emission*'),
                'abortion' = c('abort*',  'roe', 'wade', 'fetus', 'dobbs'),
                'economy' = c('inflation','price*', 'tax')
                )
# conver to a quanteda dictionary
dict_topic<-dictionary(news_dict)
set.seed(100) # setting random number seed
slda_model <- textmodel_seededlda(pol_dfm, 
                                 dictionary = dict_topic, 
                                 auto_iter = T, 
                                 residual = 5, # 5 residual topics for garbage
                                 max_iter=2000)

Again, we can extract topics from this model using the terms function. You’ll notice that there are topics that correspond to the categories in the dictionary, but there are lots of words that weren’t initially given to the model. One useful application of this approach is it can help you come up additional terms when constructing a dictionary.

terms(slda_model)%>%
  #converting to a data frame (just to make it look nicer in the notebook)
  data.frame()

And, just like with regular LDA, we can use the inferred topics to do things like track news coverage over time (note that the columns now have names based on our dictionary) Here’s how coverage of Covid-19 topic steadily dropped since 2020.

covid_df<-
  docvars(pol_dfm)%>%
  # add % of covid topic
  mutate(covid = slda_model$theta[,'covid'],
           # round down to the nearest month
         month = floor_date(date, unit='months'))%>%
  # group by month and compute the monthly average: 
  group_by(month)%>%
  summarise(covid = mean(covid))
covid_df%>%
  ggplot(aes(x=month, y=covid)) + 
  # create the line plot
  geom_line() +
  geom_point() +
  #add a marker for the start of Floyd protests:

  ylab('Average topic %') +
  # using the minimal theme: 
  theme_minimal() +
  ggtitle('Average % covid topic by month') +
  scale_x_date(breaks="3 month", date_labels = "%Ob '%y") +
  # labels at 45 degree angle
  theme(axis.text.x = element_text(angle = 45))

Lastly, the same method we used for LDAVis above should work here (unfortunately this doesn’t pick up on the topic names)

slda_vis<-'slda_vis.html'
LDAvis_standalone(slda_model, outputfile=slda_vis)
htmltools::includeHTML(slda_vis)

Structural Topic Models

Unlike traditional LDA, Structural Topic Models (paper) allow users to include additional document-level data when inferring their topics. The “prevalence model” will allow topic probabilities to vary systematically according to a characteristic of the document (such as the author or source), the “content model” will allow the term-probabilities within each topic to vary in the same way. For instance: if we think that different sources are more likely to talk about certain issues, we can explicitly model that information as part of the inference process. Here, we’re using the source variable (Fox News vs. CNN) as a predictor for the topic probabilities. The assumption (a reasonably safe one) is that the two outlets will differ systematically in the sorts of news they cover.

library(stm) # for structural topic models
# convert DFM to a format usable by STM
dfm2stm <- convert(pol_dfm, to = "stm")
# number of topics
K<-15
# random number seed 
set.seed(1000)
# run the model
stm_model <- stm(dfm2stm$documents, dfm2stm$vocab, 
                 K = K, 
                 data = dfm2stm$meta, 
                 # include source in the prevalence model
                 prevalence =  ~ source,
                 init.type = "Spectral",
                 # set verbose =T if you want to watch the model progress
                 verbose = F 
                 )

The STM package includes a special labelTopics function to retrieve the terms associated with each topic. While the highest probability terms can be a good way to identify topics, it can be somewhat unhelpful because sometimes terms show up at the topic of the list because they’re just common words in general. The FREX, lift, and score metrics all attempt to account for term frequency in some way in order to give greater weight to terms that are more distinct to each topic.

labelTopics(stm_model, topics=1:5, n=5) # note - only showing first 5 of 15 topics here
## Topic 1 Top Words:
##       Highest Prob: vaccine, testing, mask, virus, mandate 
##       FREX: vaccine, mask, virus, fauci, newsom 
##       Lift: niaid, stay-at-home, unvaccinated, allergy, azar 
##       Score: vaccine, mask, virus, fauci, cdc 
## Topic 2 Top Words:
##       Highest Prob: impeachment, prosecutors, trial, pence, indictment 
##       FREX: jury, indictment, giuliani, trial, insurrection 
##       Lift: jurors, clemency, fani, sidney, cipollone 
##       Score: indictment, riley, prosecutors, impeachment, giuliani 
## Topic 3 Top Words:
##       Highest Prob: pennsylvania, seat, carolina, endorsed, scott 
##       FREX: runoff, mail-in, hampshire, warnock, fetterman 
##       Lift: elissa, laxalt, nrsc, outraised, outspent 
##       Score: warnock, kemp, absentee, runoff, mail-in 
## Topic 4 Top Words:
##       Highest Prob: protests, obama, man, george, video 
##       FREX: floyd, protests, kennedy, confederate, bush 
##       Lift: portraits, song, confederate, knee, nfl 
##       Score: portraits, floyd, confederate, lady, protests 
## Topic 5 Top Words:
##       Highest Prob: cuomo, resign, mayor, sexual, santos 
##       FREX: santos, cuomo, ocasio-cortez, omar, blasio 
##       Lift: boylan, derosa, santos, tara, blasio 
##       Score: santos, cuomo, ocasio-cortez, blasio, sexual

You can view stm_model$theta to get the document-topic probabilities. But STM also includes the findThoughts command to help identify documents that are representative of a given topic. Here, I’m getting a document that is representative of topic 6, which appears to be related to immigration and border control issues

findThoughts(stm_model, 
             # list of texts (the dimnames part here ensures that the doc names line up with the names in the DFM)
             texts = pol_corpus[pol_dfm@Dimnames[[1]]], 
             # topic 6
             topics = 6 ,
             n = 1)
## 
##  Topic 6: 
##       An investigator generalreport found that the U.S. Environmental Protection Agency (EPA) "improperly" doled out and managed millions of dollars worth of taxpayer dollars in information technologycontracts between 2017 and 2019. The EPA's Office of Inspector General reported Wednesday that the agency had spent $52.5 million in taxpayer money on the deals, which the watchdog had investigated following a complaint about "contract and bidding irregularities with three major information technology contracts." The report said the EPA violated "Federal Acquisition Regulation requirements and contract clauses" when they "purchased 23 pieces of hardware and software equipment" through an "expiring" IT contract with Canadian IT company CGI Federal. "This purchase was outside the scope of the contract and was ultimately never used for that contract," wrote the IG's office. "The EPA then improperly solicited bids for one of two subsequent contracts and transferred the equipment to use on the new contract."  "By approving the purchase, the EPA improperly spent $641,680 in federal funds," the report continued. The report went on to reveal that the EPA "issued task orders" under the three contracts "without approval" from the agency's CIO –which the watchdog says is "required under the Federal Information Technology Acquisition Reform Act (FITARA)." These tasks orders "resulted in the EPA spending $52.5 million in taxpayer funds without proper approvals," the report read. "The agency also mismanaged these contracts with respect to monitoring property and licenses," the report continued. "For example, the EPA underreported and incorrectly identified purchased equipment in the agency's property reporting system and did not record $1.18 million in software licenses in the agency's asset management system."  The IG said in the conclusion of the report that the EPA "CIO was unaware of significant acquisitions made by the agency and was unable to appropriately review price competitions, cost reductionsand duplicate equipment." "As a result, the EPA spent $52.5 million in taxpayer dollars without the CIO's oversight," the IG concluded. The watchdog recommended that the agency's assistant administrator for mission support "[r]eview all active contracts for acquisitions of information technology hardware, softwareand services in fiscal year 2016 and later" to see if the "required" FITARA approvals had been "obtained." The IG also called on the assistant administrator to "identify cost findings in the process from hardware and software purchases that were either duplicates or unnecessary" and to "[r]equire contracting officers to maintain records" of CIO approvals, "including those under" FITARA. When pressed for comment, a spokesperson for the EPA pointed Fox News to their response in the IG report, where the agency said it "agreed" with the IG's recommendations in the report.  The confirmation ofPresident Joe Biden's pick to lead the EPA, Michael Regan, is up for a vote in the Senate this week.

The more important innovation here is that we can estimate the effect of a covariate on the topic prevalence. Here, I’m estimating the effect of source on the prevalence of all 15 topics (although I’m only showing the results for topic 6)

fx <- estimateEffect(1:15 ~ source , stm_model,
                        meta = dfm2stm$meta)
# only showing topic 6 (remove the $tables[[6]] part to see all topics)
summary(fx)$tables[[6]]
##                   Estimate  Std. Error  t value     Pr(>|t|)
## (Intercept)    0.046126691 0.003498798 13.18358 6.987575e-39
## sourceFox News 0.008347122 0.004820188  1.73170 8.340409e-02

We can also plot the results (although this is very messy for more than a handful of topics). In either case, we see Fox is generally more likely to cover the immigration topic compared to CNN.

plot.estimateEffect(fx, 
                    covariate = "source", 
                    topics = 1:6,
                    # value of fox news - cnn
                    cov.value1 = "Fox News",
                    cov.value2  = "CNN",
                    method='difference',
                    # you'll need to adjust this to keep the text from getting cut off 
                  xlim  = c(-.1, .1)
                    )

There’s also a (slightly janky) package called stminsights that will allow you to do some of this interactively. This isn’t something you can really share unless you upload it to a server somewhere, but you can use it to generate and then download some plots which you could use elsewhere.

# for some reason, the dfm data must be named "out" or else this doesn't work. 
out<-dfm2stm
# save the relevant model outputs: 
save(file='stm_model.RData', list = c('stm_model', 'out', 'fx', 'pol_corpus'))
library(stminsights)
# go to browse and find stm_model.Rdata to get this running 
run_stminsights()

Keyword Assisted Topic Models

Keyword Assisted Topic models (see: paper) are a new approach that allows both document covariates and seed terms to define topics. In short: it combines the SeededLDA approach with the Structural Topic Model. This is a pretty new method and the package is still under development, but I’m including it here because it might be of interest for those of you who are already using dictionary based methods.

The setup for the basic model is almost identical to seeded LDA, the main practical difference is that it automatically infers the \(\alpha\) and \(\beta\) parameters that control how diffuse the \(\theta\) and \(\phi\) are, which can save you the effort of having to tune these parameters.

We can use the same dictionary we implemented before. The keyATM package also provides some additional options for checking the quality of our selected keyword - notice that it throws a warning because some of the terms in our keyword list don’t actually appear in the corpus. Some terms were probably too common and got removed when we ran dfm_trim, other terms may just be too rare to be useful.

Although it will make the model run slower, you might want to increase max_docfreq when you clean the data.

library(keyATM) # keyword assisted topic models
# our news dictionary
news_dict<-list('covid' = c('covid*', 'vaccin*', 'lockdown*'),
                'natsec' = c('afghan*', 'taliban', 'putin', 
                             'china', 'ukrain*','zelensky', 'terror*'),
                'elections' = c('election', 'caucus', 'primary', 'debate*', 
                                'nomination', 'midterm', 'vote*', 'poll*'),
                'trump' = c('indictment', 'impeachment', 'mar-a-lago'),
                'jan6' = c("capitol", 'january', 'chansley','fraud'),
                'blm'  = c('floyd' , 'black', 'police', "protest*", 'blm','black-lives-matter'),
                'immigration' = c('immigrat*',  'border', 'homeland', 'migrant*'),
                'climate' = c('global','climate*','fuel', 'carbon', 'emission*'),
                'abortion' = c('abort*',  'roe', 'wade', 'fetus', 'dobbs'),
                'economy' = c('inflation','price*', 'tax')
                )

# converted to a quanteda dictionary
dict_topic<-dictionary(news_dict)
# convert the pol_dfm to a structure preferred by this package
keyATM_docs<-keyATM_read(pol_dfm)
# convert the quanteda dictionary to a format used by keyATM 
keys<-read_keywords(docs = keyATM_docs, dictionary = dict_topic)

#visualize keywords: 
key_viz<-visualize_keywords(docs = keyATM_docs, keywords = keys)
key_viz

And here’s the model training function. You’ll probably notice that this is quite a bit slower than either the standard or seeded LDA. You might want to get this running and go grab a snack. The authors recommend saving the model after it is finished running, so I’m going to do that here. To load it back up, you would just need to run `klda_model <- readRDS('klda.rds')'.

klda_model <- keyATM(
  docs              = keyATM_docs,    # text input
  no_keyword_topics = 5,              # 5 extra garbage topics
  keywords          = keys,       # our list of terms
  model             = "base",         # the base model
  
  options           = list(seed = 250) # set the random number seed inside the function
)

# save the trained model
saveRDS(klda_model, file='klda.rds')

You can get the highest probability words using the top_words function. The resulting dataframe will have a checkmark next to terms that were part of your keyword list.

top_words(klda_model)

Once again, we can use this to retrieve specific documents (and to check the quality of our topics)

election_df<-docvars(pol_dfm)%>%
  # add % of covid topic
  mutate(election= klda_model$theta[,'3_elections'])%>%
  arrange(-election)

election_df%>%
  select(url)%>%
  slice_head(n=10)

And we can use this to track news coverage over time. Here, we see peaks in the “elections” topic that roughly coincide with presidential general election seasons in the time period.

election_coverage<-election_df%>%
  mutate(# round down month
         month = floor_date(date, unit='months'))%>%
  # group by month and compute the monthly average: 
  group_by(month)%>%
  summarise(election= mean(election))

election_coverage%>%
  ggplot(aes(x=month, y=election)) + 
  # create the line plot
  geom_line() +
  geom_point() +
  ylab('Average topic %') +
  # using the minimal theme: 
  theme_minimal() +
  ggtitle('Average % election topic by month') 

Adding metadata

The real feature that distinguishes Keyword assisted topic models from seeded LDA is the ability to incorporate document meta data to predict topic prevalence a very similar fashion to the structural topic model. The syntax is here slightly different, but the basic intuition here is the same as in STM: we think that Fox News and CNN will vary systematically in the amount of attention they give to different subjects. (note: this will take a while to converge)

klda_covariate <- keyATM(
  docs              = keyATM_docs,
  no_keyword_topics = 5,
  keywords          = keys,
  model             = "covariates",
  model_settings    = list(covariates_data    = docvars(pol_dfm),
                           covariates_formula = ~ source),
  options           = list(seed = 250)
)

# save the results after running 
saveRDS(klda_covariate, file='klda_covariate.rds')

We can the top words using the same methods as before. It’s possible that the results here will have improve somewhat because the topic model is now incorporating data on the source into the metadata. However, the main finding of interest will likely come from viewing the impact of the covariates on the topic probabilities. We can retrieve these with by_strata_DocTopic() and plot them using the command below. Here, we’re plotting the prevalence for the immigration topic. The results suggest that Fox News is significantly more likely to cover this issue (at least in this sample)

#sometimes covariates will be renamed automatically
#use covariates_info(klda_covariate) to see how your covariates might have been renamed
strata_topic <- by_strata_DocTopic(
  klda_covariate, 
  # "source" here was automatically changed to sourceFox News
  by_var = "sourceFox News",
  labels = c("CNN", "Fox")
)

plot(strata_topic, var_name = 'source', show_topic= 7)

BERTopic

Lastly, while probably outside the scope of this course, I do want to highlight a final variation of the topic model known as BERTopic paper. Rather than inferring topics using a document-term-matrix, BERTopic uses an embedding model - a kind of numeric representation of text that captures semantic similarities between words, sentences or documents (good primer on this concept here). Importantly, we can use pre-existing embeddings that were trained on a huge corpus and apply them to a new set of documents, which gives us a major head-start when trying to train a model.

BERTopic generally works better on paragraphs or even single sentences, so we are going to use the same dataset here, but only take the first 5 sentences from each article. Other than that, we really don’t need to do any pre-cleaning of the data because the sentence embedding model we’re applying here was trained on regular human sentences and can use word order and context clues when producing numeric representations of sentences.

import pandas as pd
from nltk.tokenize import sent_tokenize, word_tokenize
from bertopic import BERTopic
from sentence_transformers import SentenceTransformer

# read the corpus
poldocs = pd.read_csv('https://github.com/Neilblund/APAN/raw/main/news_sample.csv')

# split the text into sentences 
sentences = [sent_tokenize(text) for text in poldocs.text]
# get the first three sentences from each text
sentences = [' '.join(x[:4]) for x in sentences]


# apply a pre-trained embedding model on the sentences
embedding_model = SentenceTransformer("all-MiniLM-L6-v2")
embeddings = embedding_model.encode(sentences, show_progress_bar=False)


topic_model = BERTopic(embedding_model = embedding_model)

# train the model
topics, probs = topic_model.fit_transform(sentences, embeddings)
 

# save the results in the working directory
topic_model.save("./bertmodel", serialization="safetensors", save_ctfidf=True, save_embedding_model=embedding_model)

The bertopic package offers a number of options for visualizing our results. The first plot shows an inter-topic distance map (similar to the kind provided by LDAVis). Notably: the BERTopic model inferred a much larger number of topics (~60) here than what we set in previous iterations, and many appear to reflect a much finer level of detail.

from bertopic import BERTopic

# loading the saved model
loaded_model = BERTopic.load("./bertmodel")

# visualize topic similarities:
loaded_model.visualize_topics()
loaded_model.visualize_barchart(topics = [ 1,3, 4, 14, 17] )
# getting topic counts by source
classes = r.poldf.source
topics_per_class = loaded_model.topics_per_class(r.poldf.text, classes=classes)

# abortion, ukraine, hunter biden, gender, and jan 6
loaded_model.visualize_topics_per_class(topics_per_class, topics = [ 1,3, 4, 14, 17], normalize_frequency= True)

Other stuff to look at

Many of these examples were adapted from the Quanteda documentation. The package documentation is here, and there is (also a tutorial here. One important area of text analysis not covered here are fully supervised models, where you take a set of labeled documents and train a model to predict a set of unlabeled documents. While useful and an important part of the text analysis field more broadly, it these models typically require a lot of hand coding before they really begin to be effective. That said, the quanteda tutorial above does go into some detail on how to train a basic text classifier in R.

If you’re really interested in going in depth, one of the best sources on this is Dan Jurafsky who makes slides, lectures, and even an entire textbook freely available online.

LS0tDQp0aXRsZTogIkFkZGl0aW9uYWwgdGV4dCBhbmFseXNpcyBpbmZvcm1hdGlvbiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIA0KDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFQpDQpMREF2aXNfc3RhbmRhbG9uZSA8LSBmdW5jdGlvbihtb2RlbCwgb3V0cHV0ZmlsZSkgew0KICByZXF1aXJlKExEQXZpcykNCiAgIyBtZXRob2QgZm9yIFdhcnBMREENCiAgaWYoY2xhc3MobW9kZWwpWzFdID09ICJXYXJwTERBIil7DQogICAganNvbiA9IGNyZWF0ZUpTT04oDQogICAgICBwaGkgPSBtb2RlbCQuX19lbmNsb3NfZW52X18kcHJpdmF0ZSR0b3BpY193b3JkX2Rpc3RyaWJ1dGlvbl93aXRoX3ByaW9yKCksDQogICAgICB0aGV0YSA9IG1vZGVsJC5fX2VuY2xvc19lbnZfXyRwcml2YXRlJGRvY190b3BpY19kaXN0cmlidXRpb25fd2l0aF9wcmlvcigpLA0KICAgICAgZG9jLmxlbmd0aCA9IG1vZGVsJC5fX2VuY2xvc19lbnZfXyRwcml2YXRlJGRvY19sZW4sDQogICAgICB2b2NhYiA9IG1vZGVsJC5fX2VuY2xvc19lbnZfXyRwcml2YXRlJHZvY2FidWxhcnksDQogICAgICB0ZXJtLmZyZXF1ZW5jeSA9IGNvbFN1bXMobW9kZWwkLl9fZW5jbG9zX2Vudl9fJHNlbGYkY29tcG9uZW50cyksDQogICAgICByZW9yZGVyLnRvcGljcyA9IEZBTFNFDQogICAgICANCiAgICApDQogIH0NCiAgIyBtZXRob2QgZm9yIHRleHRtb2RlbF9sZGENCiAgaWYoY2xhc3MobW9kZWwpWzFdID09ICd0ZXh0bW9kZWxfbGRhJyl7DQogICAganNvbjwtY3JlYXRlSlNPTigNCiAgICAgIHBoaSA9IG1vZGVsJHBoaSwNCiAgICAgIHRoZXRhID0gbW9kZWwkdGhldGEsDQogICAgICBkb2MubGVuZ3RoID0gcm93U3Vtcyhtb2RlbCRkYXRhKSwNCiAgICAgIHZvY2FiID0gY29sbmFtZXMobW9kZWwkZGF0YSksDQogICAgICB0ZXJtLmZyZXF1ZW5jeSA9IGNvbFN1bXMobW9kZWwkZGF0YSksDQogICAgICByZW9yZGVyLnRvcGljcyA9IEZBTFNFKQ0KICB9DQogIA0KICBpZihjbGFzcyhtb2RlbClbMV0gPT0gImtleUFUTV9vdXRwdXQiKXsNCiAgICBqc29uID0gY3JlYXRlSlNPTigNCiAgICAgIHBoaSA9IG1vZGVsJHBoaSwNCiAgICAgIHRoZXRhID0gbW9kZWwkdGhldGEsDQogICAgICBkb2MubGVuZ3RoID0gbW9kZWwkZG9jX2xlbnMsIA0KICAgICAgdm9jYWIgPSBtb2RlbCR2b2NhYiwNCiAgICAgIHRlcm0uZnJlcXVlbmN5ID0gbW9kZWwkd29yZF9jb3VudHMsDQogICAgICByZW9yZGVyLnRvcGljcyA9IEZBTFNFDQogICAgKQ0KICAgIA0KICB9DQogIA0KICANCiAgaWRfbmFtZTwtcGFzdGUwKCdlbGRhdmlzXycsIGdzdWIoIlteMC05XSIsICIiLCBhcy5udW1lcmljKFN5cy50aW1lKCkpKSwgY29sbGFwc2U9IiIpDQogIA0KICANCiAgY2F0KA0KICAgIHNwcmludGYoDQogICAgICAnPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyIgaHJlZj0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L2doL2JtYWJleS9weUxEQXZpc0AzLjQuMC9weUxEQXZpcy9qcy9sZGF2aXMudjEuMC4wLmNzcyI+DQoNCjxkaXYgaWQ9IiVzIiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsiPjwvZGl2Pg0KICAgIDxzY3JpcHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0Ij4NCiAgICB2YXIgbGRhdmlzX2RhdGEgPSAlcw0KDQpmdW5jdGlvbiBMREF2aXNfbG9hZF9saWIodXJsLCBjYWxsYmFjayl7DQogIHZhciBzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcJ3NjcmlwdFwnKTsNCiAgcy5zcmMgPSB1cmw7DQogIHMuYXN5bmMgPSB0cnVlOw0KICBzLm9ucmVhZHlzdGF0ZWNoYW5nZSA9IHMub25sb2FkID0gY2FsbGJhY2s7DQogIHMub25lcnJvciA9IGZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJmYWlsZWQgdG8gbG9hZCBsaWJyYXJ5ICIgKyB1cmwpO307DQogIGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJoZWFkIilbMF0uYXBwZW5kQ2hpbGQocyk7DQp9DQoNCmlmKHR5cGVvZihMREF2aXMpICE9PSAidW5kZWZpbmVkIil7DQogICAvLyBhbHJlYWR5IGxvYWRlZDoganVzdCBjcmVhdGUgdGhlIHZpc3VhbGl6YXRpb24NCiAgICFmdW5jdGlvbihMREF2aXMpew0KICAgICAgIG5ldyBMREF2aXMoIiMiICsgIiVzIiwgbGRhdmlzX2RhdGEpOw0KICAgfShMREF2aXMpOw0KfWVsc2UgaWYodHlwZW9mIGRlZmluZSA9PT0gImZ1bmN0aW9uIiAmJiBkZWZpbmUuYW1kKXsNCiAgIC8vIHJlcXVpcmUuanMgaXMgYXZhaWxhYmxlOiB1c2UgaXQgdG8gbG9hZCBkMy9MREF2aXMNCiAgIHJlcXVpcmUuY29uZmlnKHtwYXRoczoge2QzOiAiaHR0cHM6Ly9kM2pzLm9yZy9kMy52NSJ9fSk7DQogICByZXF1aXJlKFsiZDMiXSwgZnVuY3Rpb24oZDMpew0KICAgICAgd2luZG93LmQzID0gZDM7DQogICAgICBMREF2aXNfbG9hZF9saWIoImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9naC9ibWFiZXkvcHlMREF2aXNAMy40LjAvcHlMREF2aXMvanMvbGRhdmlzLnYzLjAuMC5qcyIsIGZ1bmN0aW9uKCl7DQogICAgICAgIG5ldyBMREF2aXMoIiMiICsgIiVzIiwgbGRhdmlzX2RhdGEpOw0KICAgICAgfSk7DQogICAgfSk7DQp9ZWxzZXsNCiAgICAvLyByZXF1aXJlLmpzIG5vdCBhdmFpbGFibGU6IGR5bmFtaWNhbGx5IGxvYWQgZDMgJiBMREF2aXMNCiAgICBMREF2aXNfbG9hZF9saWIoImh0dHBzOi8vZDNqcy5vcmcvZDMudjUuanMiLCBmdW5jdGlvbigpew0KICAgICAgICAgTERBdmlzX2xvYWRfbGliKCJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvZ2gvYm1hYmV5L3B5TERBdmlzQDMuNC4wL3B5TERBdmlzL2pzL2xkYXZpcy52My4wLjAuanMiLCBmdW5jdGlvbigpew0KICAgICAgICAgICAgICAgICBuZXcgTERBdmlzKCIjIiArICIlcyIsIGxkYXZpc19kYXRhKTsNCiAgICAgICAgICAgIH0pDQogICAgICAgICB9KTsNCn0NCjwvc2NyaXB0PicsIGlkX25hbWUgLHBhc3RlKGpzb24pLCBpZF9uYW1lLCBpZF9uYW1lLCBpZF9uYW1lKSwgZmlsZSAgPSAgb3V0cHV0ZmlsZSl9DQoNCiNjdXN0b20gZnVuY3Rpb24gZm9yIHN0ZW1taW5nIGFuZCB0aGVuIHVuc3RlbW1pbmcgYSBkZm0NCnN0ZW1fdW5zdGVtPC1mdW5jdGlvbihkZm0sIG1hdGNoYnk9J2ZyZXF1ZW5jeScpew0KIGlmKG1hdGNoYnkgPT0gJ3Nob3J0ZXN0Jyl7DQogICAgdm9jYWI8LWNvbG5hbWVzKGRmbSlbb3JkZXIobmNoYXIoY29sbmFtZXMoZGZtKSksIGRlY3JlYXNpbmc9VCldDQogfQ0KIGlmKG1hdGNoYnkgPT0gJ2ZyZXF1ZW5jeScpew0KICAgICB2b2NhYjwtbmFtZXMoc29ydChmZWF0ZnJlcShkZm0pLCBkZWNyZWFzaW5nPVQpKQ0KDQogfQ0KICBzdGVtbWVkPC1jaGFyX3dvcmRzdGVtKHZvY2FiLCBjaGVja193aGl0ZXNwYWNlPUZBTFNFKQ0KICBkZm08LWRmbV9yZXBsYWNlKGRmbSwgdm9jYWIsIHN0ZW1tZWQpDQogIA0KICBkZm08LSBkZm1fcmVwbGFjZShkZm0sIGZlYXRuYW1lcyhkZm0pLCB2b2NhYlttYXRjaChmZWF0bmFtZXMoZGZtKSwgc3RlbW1lZCldKQ0KICByZXR1cm4oZGZtKQ0KfQ0KICAgICAgDQpgYGANCg0KDQojIFByZWFtYmxlOiBydW5uaW5nIHRoaXMgY29kZQ0KDQpUaGVyZSdzIGEgbG90IG9mIHBhY2thZ2VzIGJlaW5nIGxvYWRlZCBoZXJlISBJZiB5b3UgZG9uJ3QgaGF2ZSBhbnkgb2YgdGhlc2UgYWxyZWFkeSBhbmQgd2FudCB0byBydW4gdGhpcyBjb2RlLCB0aGVuIHlvdSdsbCBuZWVkIHRoZXNlLiBJZiB5b3Ugd2FudCB0byBlbWJlZCB0aGUgaW50ZXJhY3RpdmUgTERBIHZpc3VhbGl6YXRpb25zIHVzaW5nIHRoZSBjdXN0b20gZnVuY3Rpb25zIGRpc2N1c3NlZCBiZWxvdywgeW91J2xsIGFsc28gbmVlZCB0byBnZXQgdGhlIGRldmVsb3BtZW50IHZlcnNpb24gb2YgYExEQXZpc2AgZXZlbiBpZiB5b3UgZG93bmxvYWRlZCB0aGUgcmVndWxhciBDUkFOIHZlcnNpb24gZWFybGllci4gVGhlIGRldmVsb3BtZW50IHZlcnNpb24gZml4ZXMgYW4gaXNzdWUgd2l0aCB0aGUgdG9waWNzIGJlaW5nIG51bWJlcmVkIGRpZmZlcmVudGx5IGluIHRoZSB2aXN1YWxpemF0aW9uIHZzIGluIFIuIElmIHlvdSB3YW50IHRvIHJ1biB0aGUgQkVSVG9waWMgbW9kZWwgaW4gUHl0aG9uIHRoZW4geW91J2xsIGFsc28gbmVlZCB0byBpbnN0YWxsIGBwYW5kYXNgLCBgbmx0a2AsIGFuZCBgYmVydG9waWNgLiANCg0KVHJ5aW5nIHRvIGNvbXBpbGUgdGhpcyBlbnRpcmUgUk1EIGlzIHByb2JhYmx5IGdvaW5nIHRvIGJlIHZlcnkgc2xvdyBhbmQgbWF5YmUgd29uJ3Qgd29yayBhdCBhbGwuIFNvIGluc3RlYWQgb2YgZG9pbmcgdGhhdCwgSSB3b3VsZCByZWNvbW1lbmQgeW91IGp1c3QgdGFrZSBvdXQgdGhlIGNodW5rcyB5b3UgcmVhbGx5IHdhbnQgdG8gdXNlIGFuZCBydW4gdGhlbSBpbnRlcmFjdGl2ZWx5LiBJbiBnZW5lcmFsLCB0aGUgZGljdGlvbmFyeSBiYXNlZCBzdHVmZiBpcyBnb2luZyB0byBydW4gbXVjaCBmYXN0ZXIgdGhhbiB0aGUgdG9waWMgbW9kZWxzLiAgDQoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQoNCiMjIyMgbm90ZSB0aGF0IGV2YWwgPSBmYWxzZSBmb3IgdGhpcyBjaHVuayAtIG9ubHkgcnVuIGl0IGlmIHlvdSBuZWVkIGl0ISANCg0KIyMjIyBhZGRpdGlvbmFsIHF1YW50ZWRhIHBhY2thZ2VzOiANCmluc3RhbGwucGFja2FnZXMoJ3F1YW50ZWRhLnRleHRtb2RlbHMnKQ0KaW5zdGFsbC5wYWNrYWdlcygncXVhbnRlZGEudGV4dHBsb3RzJykNCg0KIyMjIyBkZXZlbG9wbWVudCB2ZXJzaW9uIG9mIExEQXZpcyAtIGZpeGVzIGEgcHJvYmxlbSB3aXRoIHRvcGljIG9yZGVyaW5nDQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImNwc2lldmVydC9MREF2aXMiKQ0KIyMjI2FkZGl0aW9uYWwgZGljdGlvbmFyaWVzOiANCg0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJrYmVub2l0L3F1YW50ZWRhLmRpY3Rpb25hcmllcyIpIA0KcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoInF1YW50ZWRhL3F1YW50ZWRhLnNlbnRpbWVudCIpDQojIyNTZWVkZWQgTERBIA0KDQppbnN0YWxsLnBhY2thZ2VzKCJzZWVkZWRsZGEiKQ0KIyMjIFN0cnVjdHVyYWwgdG9waWMgbW9kZWwgDQppbnN0YWxsLnBhY2thZ2VzKCJzdG0iKQ0KIyMjIGFkZGl0aW9uYWwgcGFja2FnZSBmb3Igdmlld2luZyBTVE0gb3V0cHV0OiANCmluc3RhbGwucGFja2FnZXMoInN0bWluc2lnaHRzIikNCg0KIyMjS2V5d29yZCBMREENCmluc3RhbGwucGFja2FnZXMoIktleUFUTSIpDQoNCg0KYGBgDQoNCiMgU2V0dXA6IGNsZWFuaW5nIHRoZSBkYXRhDQoNClNpbmNlIG1vc3Qgb2YgdGhlc2UgbW9kZWxzIGFyZSBnb2luZyB0byB1c2UgdGhlICJiYWctb2Ytd29yZHMiIGFzc3VtcHRpb24sIHdlIGNhbiBkbyB0aGUgc2FtZSBjbGVhbmluZyBzdGVwcyBmb3IgZWFjaC4gVGhlIGNvZGUgYmVsb3cgc2hvdWxkIGJlIGZhbWlsaWFyIHRvIHlvdSBhbHJlYWR5LCBhbmQgZHJhd3MgZnJvbSBhIGNvcnB1cyBvZiBuZXdzIHN0b3JpZXMgc2NyYXBlZCBmcm9tIENOTiBBTkQgRm94IE5ld3MgZnJvbSAyMDIwIHRocm91Z2ggU2VwdGVtYmVyIDIwMjMuIFRoZSAidGV4dCIgY29sdW1uIGNvbnRhaW5zIHRoZSB0ZXh0LCBhbmQgdGhlICJzb3VyY2UiIGNvbHVtbiBjb250YWlucyB0aGUgc291cmNlLiBUaGVyZSBhcmUgYWxzbyBkYXRlcyBhc3NvY2lhdGVkIHdpdGggZWFjaCBhcnRpY2xlLiANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICMgZm9yIGRhdGEgbWFuaXB1bGF0aW9uDQpsaWJyYXJ5KHF1YW50ZWRhKSAgIyBmb3IgY2xlYW5pbmcgYW5kIGFuYWx5c2lzIG9mIHRleHQNCmxpYnJhcnkocXVhbnRlZGEudGV4dHN0YXRzKSAjIGV4dHJhIHF1YW50ZWRhIGNvbW1hbmRzDQpsaWJyYXJ5KHF1YW50ZWRhLnRleHRwbG90cykgIyBmb3IgcGxvdHRpbmcgdGhlIGtleW5lc3Mgc3RhdGlzdGljDQoNCg0KcG9sZGY8LXJlYWRfY3N2KCdodHRwczovL2dpdGh1Yi5jb20vTmVpbGJsdW5kL0FQQU4vcmF3L21haW4vbmV3c19zYW1wbGUuY3N2JykNCg0KIyBub3JtYWxpemluZyBzb21lIGFwb3N0cm9waGVzIGFuZCBxdW90ZXMgKHN1cnByaXNpbmdseSBpbXBvcnRhbnQhKQ0KcG9sZGYkdGV4dDwtc3RyX3JlcGxhY2VfYWxsKHBvbGRmJHRleHQsIlx1MjAxQ3xcdTIwMUQiLCAnIicpJT4lDQogIHN0cl9yZXBsYWNlX2FsbCguLCJcdTIwMTl8XHUyMDE4IiwgIiciKQ0KIyBjb252ZXJ0IHRvIGEgY29ycHVzDQpwb2xfY29ycHVzPC1wb2xkZiU+JQ0KICB3aXRoKC4sY29ycHVzKHRleHQsIA0KICAgICAgICAgICAgICAgIGRvY3ZhcnMgPSBkYXRhLmZyYW1lKHVybCwgaGVhZGxpbmUsICBkYXRlLCBzb3VyY2UpKSkNCg0KIyB0b2tlbml6ZSB0aGUgdGV4dHMNCnBvbF90b2tlbnM8LXRva2Vucyhwb2xfY29ycHVzLCANCiAgICAgICAgICAgICAgICAgICB3aGF0ID0gJ3dvcmQnLA0KICAgICAgICAgICAgICAgICAgICMgdGhpcyBrZWVwcyBjaGFyYWN0ZXJpc3RpY3MgYWJvdXQgdGhlIGRvY3VtZW50cyBsaWtlIHRoZSBoZWFkbGluZSBhbmQgdXJsDQogICAgICAgICAgICAgICAgICAgcmVtb3ZlX3B1bmN0ID1UUlVFLA0KICAgICAgICAgICAgICAgICAgIHJlbW92ZV9zeW1ib2xzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICByZW1vdmVfbnVtYmVycyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgcmVtb3ZlX3VybCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgcmVtb3ZlX3NlcGFyYXRvcnMgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfZG9jdmFycyA9IFRSVUUNCiAgICAgICAgICAgICAgICAgICApDQojIHJlbW92ZSBzdG9wIHdvcmRzIA0KdG9rc19ub3N0b3AgPC0gdG9rZW5zX3NlbGVjdChwb2xfdG9rZW5zLCBwYXR0ZXJuID0gc3RvcHdvcmRzKCJlbiIpLCBzZWxlY3Rpb24gPSAicmVtb3ZlIikNCiMgY29udmVydCB0byBhIGRvY3VtZW50LWZlYXR1cmUgbWF0cml4DQpwb2xfZGZtPC0gZGZtKHRva3Nfbm9zdG9wKQ0KIyBzdGVtIGFuZCB0aGVuIHVuc3RlbSB3b3JkcyAodXNpbmcgY3VzdG9tIGZ1bmN0aW9uIGluIHByZWFtYmxlIHRvIHRoaXMgZG9jdW1lbnQpDQpwb2xfZGZtPC1zdGVtX3Vuc3RlbShwb2xfZGZtKSANCiMgaWYgeW91IGRvbid0IHdhbnQgdG8gdXNlIHRoZSBzdGVtX3Vuc3RlbSBmdW5jdGlvbiwgcnVuIHRoaXMgaW5zdGVhZDogIA0KIyBwb2xfZGZtPC1kZm1fd29yZHN0ZW0oZGZtKSAgDQoNCiMgcmVtb3ZlIGZlYXR1cmVzIHRoYXQgb2NjdXIgbGVzcyB0aGFuIDEwIHRpbWVzDQpwb2xfZGZtPC1kZm1fdHJpbShwb2xfZGZtLCBtaW5fZG9jZnJlcT0gMTApDQojIHJlbW92ZSBmZWF0dXJlcyB0aGF0IG9jY3VyIGluIG1vcmUgdGhhbiAxMCUgb2YgZG9jdW1lbnRzIA0KIyBpbmNyZWFzaW5nIG1heF9kb2NmcmVxIG1heSBpbXByb3ZlIHJlc3VsdHMsIGJ1dCBpdCB3aWxsIHRha2UgbG9uZ2VyIGZvciBtb2RlbHMgdG8gY29udmVyZ2UNCnBvbF9kZm08LSBkZm1fdHJpbShwb2xfZGZtLCBtYXhfZG9jZnJlcSA9IDAuMSwgZG9jZnJlcV90eXBlID0gInByb3AiKQ0KYGBgDQoNCg0KDQojIEtleXdvcmRzIGluIGNvbnRleHQgDQoNCktXSUMgc3RhbmRzIGZvciAia2V5d29yZHMgaW4gY29udGV4dCIuIFdlIGNhbiB1c2UgS1dJQyB0byBmaW5kIHN0b3JpZXMgdGhhdCBjb250YWluIGEgdGVybSwgYW5kIGdldCB0aGUgdGV4dCBzdXJyb3VuZGluZyB0aGF0IHdvcmQuIFRoaXMgY29kZSB3b3VsZCBwdWxsIGFsbCBkb2N1bWVudHMgY29udGFpbmluZyB0ZXJtcyB0aGF0IHN0YXJ0ZWQgd2l0aCAiaW1taWciIChzbzogaW1taWdyYW50LCBpbW1pZ3JhdGlvbiwgaW1taWdyYW50cyBldGMuKSBhbG9uZyB3aXRoIGEgd2luZG93IG9mIDMgd29yZHMgb24gZWl0aGVyIHNpZGUuIA0KDQoobm90ZSB0aGF0IHRoaXMgaXMgYWN0dWFsbHkgcHVsbGluZyBmcm9tIHRoZSBgYHBvbF90b2tlbnNgYCBvYmplY3QgcmF0aGVyIHRoYW4gdGhlIERGTSwgdGhpcyBhcHByb2FjaCBkb2Vzbid0IHJlYWxseSByZXF1aXJlIGFueSBjbGVhbmluZyB0byB1c2UpDQoNCmBgYHtyfQ0KIyBrZXl3b3JkcyBpbiBjb250ZXh0DQprd19pbW1pZyA8LSBrd2ljKHBvbF90b2tlbnMsIHBhdHRlcm4gPSAgImltbWlnKiIsIHdpbmRvdz0zKQ0KIyB2aWV3IHRoZSBmaXJzdCAxMA0KaGVhZChrd19pbW1pZywgNSkNCmBgYA0KDQpGb3IgeW91ciBvd24gd29yaywgeW91IG1heSB3YW50IHRvIGdldCBtb3JlIGNvbnRleHQgYnkgaW5jcmVhc2luZyB0aGUgYGB3aW5kb3dgYCBhcmd1bWVudCBpbiB0aGUgS1dJQyBjb21tYW5kLiBZb3UgbWlnaHQgYWxzbyBjb25zaWRlciB3cml0aW5nIHRoZXNlIHJlc3VsdHMgdG8gYSBjc3YgZmlsZSBzbyB5b3UgY2FuIHJlYWQgdGhlbSBpbiBhIHNwcmVhZHNoZWV0LiBLV0lDIGRvZXNuJ3QgYXV0b21hdGljYWxseSBwcm92aWRlIHlvdSB3aXRoIG5pY2UgbG9va2luZyBwbG90cywgYnV0IGl0IGNhbiBiZSBhIHZlcnkgZ29vZCB3YXkgdG8gZG8gdGhpbmdzIGxpa2UgZGV2ZWxvcCBhIGRpY3Rpb25hcnkgb3IgZ2V0IGEgZ2VuZXJhbCBxdWFsaXRhdGl2ZSBzZW5zZSBvZiBob3cgc291cmNlcyBhcmUgZGlzY3Vzc2luZyBzb21ldGhpbmcuDQoNCiMgIEtleW5lc3MNCg0KS2V5bmVzcyBpcyBhIG1lYXN1cmUgb2YgcmVsYXRpdmUgZnJlcXVlbmN5IHRoYXQgY2FuIGJlIHVzZWZ1bCBmb3IgZmluZGluZyB0ZXJtcyB0aGF0IGFyZSBtb3JlIGNvbW1vbiBpbiBwYXJ0aWN1bGFyIHN1YnNldHMgb2YgZG9jdW1lbnRzLiBGb3IgaW5zdGFuY2UsIGlmIHdlIHdhbnQgdG8gZmluZCB0ZXJtcyB0aGF0IGFyZSBtb3JlIGNvbW1vbiBpbiBGb3ggTmV3cyBhcnRpY2xlcyBjb21wYXJlZCB0byBDTk4sIHdlIGNvdWxkIGp1c3QgcnVuIHRoZSBmb2xsb3dpbmc6IA0KDQpgYGB7cn0NCg0KdHN0YXRfa2V5IDwtIHRleHRzdGF0X2tleW5lc3MocG9sX2RmbSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGZpbmQgZGlzdGluY3RpdmUgdGVybXMgZm9yIEZveCBuZXdzIGNvbXBhcmVkIHRvIENOTiA6DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBkb2N2YXJzKHBvbF9kZm0pJHNvdXJjZSA9PSAiRm94IE5ld3MiKQ0KdGV4dHBsb3Rfa2V5bmVzcyh0c3RhdF9rZXkpDQoNCmBgYA0KDQoNCldlIGNvdWxkIGFsc28gZm9jdXMgb24gYSBzdWJzZXQgb2YgYXJ0aWNsZXMgdG8gc2VlIGhvdyBzb3VyY2VzIGRpZmZlciBpbiB0aGVpciBjb3ZlcmFnZSBvZiBzaW1pbGFyIHRvcGljcy4gSGVyZSwgd2UncmUgbG9va2luZyBhdCBrZXl3b3JkcyB0aGF0IGFyZSBzdHJvbmdseSBhc3NvY2lhdGVkIHdpdGggZG9jdW1lbnRzIHRoYXQgbWVudGlvbiBIdW50ZXIgQmlkZW4uIFRoZSByZXN1bHRzIHN1Z2dlc3QgdGhhdCBGb3ggYW5kIENOTiBoYXZlIHNvbWUgdmVyeSBkaXN0aW5jdCB0ZXJtaW5vbG9neSB3aGVuIGRpc2N1c3NpbmcgdGhpcyBpc3N1ZTogIA0KDQpgYGB7cn0NCmh1bnRlcl9kZm08LWRmbV9zdWJzZXQocG9sX2RmbSwgDQogICAgICAgICAgICAgICAgICAgICAgICMgIGdldCBhIHN1YnNldCBvZiBhcnRpY2xlcyB0aGF0IG1lbnRpb24gSHVudGVyIEJpZGVuDQogICAgICAgICAgICAgICAgICAgICAgIHN1YnNldCAgPSBzdHJfZGV0ZWN0KHBvbF9jb3JwdXMsICJIdW50ZXIgQmlkZW4iKSkNCnRzdGF0X2tleSA8LSB0ZXh0c3RhdF9rZXluZXNzKGh1bnRlcl9kZm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBkb2N2YXJzKGh1bnRlcl9kZm0pJHNvdXJjZSA9PSAiRm94IE5ld3MiKQ0KdGV4dHBsb3Rfa2V5bmVzcyh0c3RhdF9rZXkpDQoNCmBgYA0KDQoNCiMgRGljdGlvbmFyeSBNZXRob2RzDQoNCldlIGNhbiB1c2UgdGhlIGRpY3Rpb25hcnkgYXBwcm9hY2ggdG8gc2ltcGx5IG1hdGNoIHRlcm1zIGFnYWluc3QgYSBkaWN0aW9uYXJ5IG9mIHRlcm1zIHdlJ3JlIGludGVyZXN0ZWQgaW4uIFRoZSBwb2xpY3kgYWdlbmRhcyBkaWN0aW9uYXJ5IGNvbnRhaW5zIGEgbGlzdCBvZiB0ZXJtcyBhc3NvY2lhdGVkIHdpdGggbWFqb3IgcG9saWN5IGFyZWFzLiBIZXJlLCB3ZSBhcHBseSB0aGUgYWdlbmRhcyBjb2RlYm9vayB0byB0aGUgdG9rZW5zIGxpc3QsIHdoaWNoIHJlbW92ZXMgYWxsIHRlcm1zIHRoYXQgYXJlbid0IHBhcnQgb2YgdGhlIGRpY3Rpb25hcnkuIA0KDQpgYGB7cn0NCiMgZG93bmxvYWQgdGhlIHBvbGljeSBhZ2VuZGFzIGNvZGVib29rIChkaXNjdXNzZWQgaGVyZSAjaHR0cHM6Ly93d3cuYWxtZW5kcm9uLmNvbS90cmlidW5hL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE3LzA1L0NBUDIwMTN2Mi5wZGYpDQpwb2xpY3lBZ2VuZGFzPC1yZWFkUkRTKHVybCgnaHR0cHM6Ly9naXRodWIuY29tL05laWxibHVuZC9BUEFOL3Jhdy9tYWluL3BvbGljeV9hZ2VuZGFzX2RpY3Rpb25hcnkucmRzJykpDQoNCmltbWlnX3Rva2VuczwtdG9rZW5zX3N1YnNldChwb2xfdG9rZW5zLCANCiAgICAgICAgICAgICAgICAgICAgICAgIyAgZ2V0IGEgc3Vic2V0IG9mIGFydGljbGVzIHRoYXQgbWVudGlvbiBIdW50ZXIgQmlkZW4NCiAgICAgICAgICAgICAgICAgICAgICAgc3Vic2V0ICA9IHN0cl9kZXRlY3QocG9sX2NvcnB1cywgImltbWlncioiKSkNCg0KIyBjb252ZXJ0IHRvIGEgcXVhbnRlZGEgZGljdGlvbmFyeSBmb3JtYXQNCnBvbGljeWFnZW5kYXMuZGljdCA8LSBkaWN0aW9uYXJ5KHBvbGljeUFnZW5kYXMpDQoNCiMgYXBwbHkgdGhlIGRpY3Rpb25hcnkNCmFnZW5kYV9wb2xpdGljcyA8LSB0b2tlbnNfbG9va3VwKHBvbF90b2tlbnMsIGRpY3Rpb25hcnkgPSBwb2xpY3lhZ2VuZGFzLmRpY3QsIGxldmVscyA9IDEpDQoNCiMgY2hlY2sgb3V0IHRoZSBmaXJzdCByZXN1bHQ6IA0KYWdlbmRhX3BvbGl0aWNzW1sxXV0NCg0KYGBgDQoNCg0KV2UgY2FuIGNvbnZlcnQgdGhpcyBsaXN0IG9mIHRva2VucyB0byBhIGRvY3VtZW50IGZlYXR1cmUgbWF0cml4LiBBIERGTSB3aWxsIGhhdmUgb25lIHJvdyBwZXIgZG9jdW1lbnQgYW5kIG9uZSBjb2x1bW4gcGVyIGZlYXR1cmUsIHRoZSBjZWxscyB3aWxsIGNvbnRhaW4gY291bnRzIG9mIHRoZSBudW1iZXIgb2YgdGltZXMgYSBnaXZlbiBmZWF0dXJlIG9jY3VycyBpbiBlYWNoIGRvY3VtZW50LiBJbiB0aGlzIGNhc2UsIG91ciBERk0gd2lsbCBoYXZlIG9uZSBjb2x1bW4gZm9yIGVhY2ggb2YgdGhlIHRvcGljcyBpbiB0aGUgcG9saWN5IGFnZW5kYXMgZGljdGlvbmFyeS4gV2UgY2FuIHVzZSB0aGlzIHRvIGRvIHRoaW5ncyBsaWtlIGNvdW50IHRoZSBudW1iZXIgb2YgZG9jdW1lbnRzIHdoaWNoIGFyZSBhYm91dCBzb21lIHRvcGljLiANCg0KDQpgYGB7cn0NCiMgY29udmVydCB0byBhIGRmbQ0KYWdlbmRhX2RmbTwtZGZtKGFnZW5kYV9wb2xpdGljcykNCiMgbG9vayBhdCB0aGUgZmlyc3QgZmV3IHJvd3MNCmhlYWQoYWdlbmRhX2RmbSwgbj01KQ0KYGBgDQoNCmBgdGV4dHN0YXRfZnJlcXVlbmN5YGAgZ2l2ZXMgdXMgc29tZSBiYXNpYyBzdGF0cyBhYm91dCBvdXIgZG9jdW1lbnQgZmVhdHVyZXMuIFRoZSAiZnJlcXVlbmN5IiBjb2x1bW4gaXMgdGhlIG51bWJlciBvZiB0aW1lcyBhIHBvbGljeSBhZ2VuZGEgdG9waWMgb2NjdXJzLiBUaGUgImRvY2ZyZXEiIGNvbHVtbiBpcyB0aGUgbnVtYmVyIG9mIGRvY3VtZW50cyB3aGljaCBjb250YWluIHRoYXQgYWdlbmRhIHRvcGljIChyZWdhcmRsZXNzIG9mIHdoZXRoZXIgaXQgb2NjdXJzIG11bHRpcGxlIHRpbWVzIGluIGEgc2luZ2xlIGRvY3VtZW50KS4gRnJvbSB0aGlzLCB3ZSBjYW4gc2VlIHRoYXQgImhlYWx0aGNhcmUiIGlzIHRoZSBtb3N0IGNvbW1vbmx5IG9jY3VycmluZyBhZ2VuZGEgdG9waWMuIA0KDQpgYGB7cn0NCmFnZW5kYV9mcmVxPC10ZXh0c3RhdF9mcmVxdWVuY3koYWdlbmRhX2RmbSkNCmhlYWQoYWdlbmRhX2ZyZXEpDQpgYGANCg0KUHV0IHRoZW0gYWxsIGluIGEgaG9yaXpvbnRhbCBiYXJwbG90OiANCg0KYGBge3IgYWdlbmRhcGxvdH0NCmFnZW5kYV9mcmVxJT4lDQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoZmVhdHVyZSwgZG9jZnJlcSksIHk9ZG9jZnJlcSkpICsgDQogIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JywgZmlsbD0nbGlnaHRibHVlJykgKw0KICB4bGFiKCdwb2xpY3kgYWdlbmRhJykgKyANCiAgeWxhYignbnVtYmVyIG9mIGRvY3VtZW50cyBtZW50aW9uaW5nJykgKw0KICBjb29yZF9mbGlwKCkgKw0KICAjIHVzaW5nIHRoZSBtaW5pbWFsIHRoZW1lOiANCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgZ2d0aXRsZSgnRG9jdW1lbnQgZnJlcXVlbmN5IG9mIHBvbGljeSBhZ2VuZGEgdGVybXMgaW4gQ05OIHBvbGl0aWNzIGFydGljbGVzJykNCmBgYA0KDQoNCiMjIEFkZGl0aW9uYWwgZGljdGlvbmFyaWVzL2NvbW1hbmRzDQoNClF1YW50ZWRhIGRvZXMgaGF2ZSBzb21lIGFkZGl0aW9uYWwgZGljdGlvbmFyaWVzIHRoYXQgbWF5IGJlIG9mIHVzZSBmb3Igc29tZSBvZiB5b3UuIFRoZXNlIGNhbiBiZSBhY2Nlc3NlZCB0aHJvdWdoIHRoZSBbUXVhbnRlZGEuZGljdGlvbmFyaWVzXShodHRwczovL2dpdGh1Yi5jb20va2Jlbm9pdC9xdWFudGVkYS5kaWN0aW9uYXJpZXMpIHBhY2thZ2UgYW5kIHRoZSBbcXVhbnRlZGEuc2VudGltZW50XShodHRwczovL2dpdGh1Yi5jb20vcXVhbnRlZGEvcXVhbnRlZGEuc2VudGltZW50KSBwYWNrYWdlLiBBbHRob3VnaCB0aGVzZSBkb24ndCBhcHBlYXIgdG8gYmUgb24gQ1JBTiB5ZXQsIHlvdSBjYW4gaW5zdGFsbCB0aGVtIGJ5IHJ1bm5pbmcgdGhlIGNvbW1hbmQgYmVsb3csIGFuZCB0aGVuIGxvYWRpbmcgdGhlbSB0aGUgd2F5IHlvdSB3b3VsZCBsb2FkIGFueSBvdGhlciBsaWJyYXJ5LiBUaGUgYXNzb2NpYXRlZCBnaXRodWIgcGFnZSBoYXMgaW5mb3JtYXRpb24gb24gdGhlIGluY2x1ZGVkIGRpY3Rpb25hcnkgZmlsZXMuIA0KDQoNCg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1Yigia2Jlbm9pdC9xdWFudGVkYS5kaWN0aW9uYXJpZXMiKSANCnJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJxdWFudGVkYS9xdWFudGVkYS5zZW50aW1lbnQiKQ0KYGBgDQoNCg0KVGhlIFt0ZXh0c3RhdF9wb2xhcml0eV0oaHR0cHM6Ly9yZHJyLmlvL2dpdGh1Yi9xdWFudGVkYS9xdWFudGVkYS5zZW50aW1lbnQvbWFuL3RleHRzdGF0X3BvbGFyaXR5Lmh0bWwpIGZ1bmN0aW9uIHdpbGwgYXV0b21hdGljYWxseSBhcHBseSBhIGRpY3Rpb25hcnkgYW5kIG1lYXN1cmUgc2VudGVuY2UgcG9sYXJpdHkgYnkgZ2V0dGluZyB0aGUgbG9nIG9mIHBvc2l0aXZlL25lZ2F0aXZlIHRlcm1zIGluIHRoZSB0ZXh0LiBUaGUgW3RleHRzdGF0X3ZhbGVuY2VdKGh0dHBzOi8vcmRyci5pby9naXRodWIvcXVhbnRlZGEvcXVhbnRlZGEuc2VudGltZW50L21hbi90ZXh0c3RhdF92YWxlbmNlLmh0bWwpIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHRvIGNvbXB1dGUgc2NvcmVzIGZvciBkaWN0aW9uYXJpZXMgdGhhdCBoYXZlIG11bHRpcGxlIGNhdGVnb3JpZXMgcmF0aGVyIHRoYW4ganVzdCBwb3NpdGl2ZSB2cy4gbmVnYXRpdmUuDQoNCkhlcmUncyBhbiBleGFtcGxlIG9mIGhvdyB5b3UgbWlnaHQgdXNlIHRoZSBgdGV4dHN0YXRfcG9sYXJpdHlgIGNvbW1hbmQgZnJvbSB0aGlzIHBhY2thZ2UgYWxvbmcgd2l0aCB0aGUgW0xleGljb2RlciBTZW50aW1lbnQgRGljdGlvbmFyeV0oaHR0cHM6Ly93d3cuc25zb3Jva2EuY29tL2RhdGEtbGV4aWNvZGVyLykuIEluIHRoaXMgZXhhbXBsZSwgd2UncmUgbWVhc3VyaW5nIHRoZSBkb2N1bWVudCBwb2xhcml0eSwgdGhlbiBncmFwaGluZyB0aGUgYXZlcmFnZSBzZW50aW1lbnQgZm9yIGRvY3VtZW50cyBmb3IgZWFjaCBzb3VyY2UgYW5kIGRlcGVuZGluZyBvbiB3aGV0aGVyIHRoZXkgbWVudGlvbiBpbW1pZ3JhdGlvbi4gVGhlIHJlc3VsdHMgc3VnZ2VzdCB0aGF0IEZveCBOZXdzIHRlbmRzIHRvIGdpdmUgbW9yZSBuZWdhdGl2ZSBjb3ZlcmFnZSB3aGVuIGRpc2N1c3NpbmcgaW1taWdyYXRpb24gY29tcGFyZWQgdG8gdGhlaXIgY292ZXJhZ2Ugb2Ygb3RoZXIgdG9waWNzLCB3aGVyZSBhcyBDTk4gY292ZXJzIGltbWlncmF0aW9uIHdpdGggYSBzbGlnaHRseSBtb3JlIHBvc2l0aXZlIHZhbGVuY2UuIA0KDQpgYGB7cn0NCmxpYnJhcnkocXVhbnRlZGEuc2VudGltZW50KQ0KIyBsb2FkIHRoZSBsZXhpY29kZXIgc2VudGltZW50IGRpY3Rpb25hcnkNCmxzZDwtcXVhbnRlZGEuc2VudGltZW50OjpkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNQ0KDQpwb2xhcml0eTwtdGV4dHN0YXRfcG9sYXJpdHkocG9sX2NvcnB1cywgZGljdGlvbmFyeT1sc2QpJT4lDQogICMgYWRkIHBvbGFyaXR5IHRvIGV4aXN0aW5nIGRvY3VtZW50IHZhcmlhYmxlcw0KICBiaW5kX2NvbHMoZG9jdmFycyhwb2xfY29ycHVzKSklPiUNCiAgIyBhbHNvIGFkZCBhIGNvbHVtbiBmb3IgbWVudGlvbnMgb2YgaW1taWdyYXRpb24NCiAgbXV0YXRlKG1lbnRpb25zX2ltbWlncmF0aW9uID0gc3RyX2RldGVjdChwb2xfY29ycHVzLCJpbW1pZ3IqIikpDQoNCiMgcGxvdCBwb2xhcml0eSBmb3IgYWxsIGZvdXIgZ3JvdXBzDQpnZ3Bsb3QocG9sYXJpdHksIGFlcyh4PXNvdXJjZSwgeT1zZW50aW1lbnQsIGZpbGw9bWVudGlvbnNfaW1taWdyYXRpb24pKSArIA0KICBnZW9tX2JveHBsb3Qobm90Y2g9VFJVRSkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyB0ZXN0IGFzIGFuIGludGVyYWN0aW9uIHRlcm06DQojIyBzdW1tYXJ5KGxtKHNlbnRpbWVudCB+IHNvdXJjZSAqIG1lbnRpb25zX2ltbWlncmF0aW9uLCBkYXRhPXBvbGFyaXR5KSkNCg0KYGBgDQojIyBNYWtpbmcgYSBjdXN0b20gZGljdGlvbmFyeQ0KDQpGaW5hbGx5LCBrZWVwIGluIG1pbmQgdGhhdCB5b3UgY2FuIGNyZWF0ZSBhIGRpY3Rpb25hcnkgZnJvbSBhIG5hbWVkIGxpc3QuIFNvIGlmIHlvdSB3YW50IHRvIGNyZWF0ZSB5b3VyIG93biBmb3IgYSBzcGVjaWZpYyBkb21haW4gb3IgbGFuZ3VhZ2UsIG9yIGV4dGVuZCBhbiBleGlzdGluZyBvbmUsIHRoZSBzeW50YXggc2hvdWxkIGJlIHJlYXNvbmFibHkgc3RyYWlnaHRmb3J3YXJkLiANCg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmN1c3RvbV9saXN0PC1saXN0KENPVklEICA9YygnY292aWQnLCdjb3JvbmF2aXJ1cycsICd2YWNjaW4qJywgJ2xvY2sgZG93bicpLA0KICAgICAgICAgICAgICAgICAgSmFuNiA9IGMoICJqYW4qIDYqIiwgImNhcGl0b2wiLCAnaW5zdXJyZWN0aW9uKicpKQ0KDQoNCg0KY3VzdG9tX2RpY3QgPC0gZGljdGlvbmFyeShjdXN0b21fbGlzdCkNCnRvazwtdG9rZW5zX2xvb2t1cChwb2xfdG9rZW5zLCBkaWN0aW9uYXJ5PWN1c3RvbV9kaWN0KQ0KDQoNCnRhYmxlKHVubGlzdCh0b2spKQ0KDQpgYGANCg0KDQpBbnkgb2YgdGhlc2UgYXBwcm9hY2hlcyBhcmUgYSBnb29kIHN0YXJ0aW5nIHBvaW50IGFuZCBJIHRoaW5rIHlvdSBjYW4gZGVmaW5pdGVseSB1c2UgdGhlbSBmb3IgeW91ciBhbmFseXNlcywgYnV0IHlvdSBzaG91bGQgYmUgd2lsbGluZyB0byBhZG1pdCB0aGVpciBsaW1pdGF0aW9ucyB3aGVuIGFwcGxpZWQgb3V0c2lkZSB0aGUgYXJlYSB0aGV5IHdlcmUgZGVzaWduZWQgZm9yLiBDaGVjayBvdXQgW3RoaXMgcGFwZXJdKGh0dHBzOi8vd3d3LmNhbWJyaWRnZS5vcmcvY29yZS9qb3VybmFscy9wb2xpdGljYWwtYW5hbHlzaXMvYXJ0aWNsZS90ZXh0LWFzLWRhdGF0aGUtcHJvbWlzZS1hbmQtcGl0ZmFsbHMtb2YtYXV0b21hdGljLWNvbnRlbnQtYW5hbHlzaXMtbWV0aG9kcy1mb3ItcG9saXRpY2FsdGV4dHMvRjdBQUM4QjI5MDk0NDE2MDNGRUIyNUMxNTY0NDhGMjApIGZvciBzb21lIGRpc2N1c3Npb24gb2YgdGhlIGxpbWl0YXRpb25zIG9mIGRpY3Rpb25hcnkgYmFzZWQgYXBwcm9hY2hlcy4gDQoNCg0KIyBUb3BpYyBNb2RlbGluZw0KDQojIyBMREENCg0KTGF0ZW50IERpcmljaGxldCBBbGxvY2F0aW9uIChMREEpIGFsc28ga25vd24gYXMgdG9waWMgbW9kZWxpbmcgaXMgd2lkZWx5IHVzZWQgYXBwcm9hY2ggZm9yIHVuc3VwZXJ2aXNlZCBkb2N1bWVudCBjbGFzc2lmaWNhdGlvbi4gIlVuc3VwZXJ2aXNlZCIgaW4gdGhpcyBjYXNlLCBtZWFucyB0aGF0IHlvdSBkb24ndCBuZWVkIGFueSBwcmlvciBrbm93bGVkZ2UgYWJvdXQgdGhlIGNvcnB1cyBpbiBvcmRlciB0byBtYWtlIHVzZSBvZiBpdC4gSW5zdGVhZCwgYW4gTERBIG1vZGVsIHdpbGwgYXR0ZW1wdCB0byBtb2RlbCBkb2N1bWVudHMgYXMgYXMgYSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gb3ZlciB0b3BpY3MsIGFuZCB0b3BpY3MgYXMgYSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gb3ZlciB3b3Jkcy4gVGhpcyBpcyBkb25lIGF1dG9tYXRpY2FsbHkgLSBhbGwgeW91IHJlYWxseSBuZWVkIHRvIGRvIGlzIGNsZWFuIHRoZSB0ZXh0IGFuZCBjaG9vc2UgYSBudW1iZXIgb2YgdG9waWNzLiBUaGF0IHNhaWQsIHRoaXMgYWxzbyBtZWFucyB0aGF0IHRoZSByZXN1bHRzIGNhbiBiZSBzb21ld2hhdCBoYXJkIHRvIGludGVycHJldDogeW91J2xsIGhhdmUgdG8gbWFrZSBhbiBlZHVjYXRlZCBndWVzcyBhYm91dCB3aGF0IHRvcGljcyB5b3UncmUgZmluZGluZyBieSBsb29raW5nIGF0IHdvcmRzIHRoYXQgb2NjdXIgd2l0aCBhIGhpZ2hlciBmcmVxdWVuY3kgaW4gYSBnaXZlbiB0b3BpYy4NCg0KTm90ZTogSWYgeW91J3JlIGxvb2tpbmcgZm9yIGEgcXVpY2sgbW9zdGx5IG5vbi10ZWNobmljYWwgcHJpbWVyIG9uIHRoaXMsIEkgdGhpbmsgRWR3aW4gQ2hlbidzIFtibG9nIHBvc3RdKGh0dHBzOi8vYmxvZy5lY2hlbi5tZS8yMDExLzA4LzIyL2ludHJvZHVjdGlvbi10by1sYXRlbnQtZGlyaWNobGV0LWFsbG9jYXRpb24vKSBpcyBhIGdvb2Qgc3RhcnRpbmcgcG9pbnQsIGJ1dCB0aGVyZSdzIGEgbG90IG9mIGluZm9ybWF0aW9uIGFib3V0IHRoZXNlIG9ubGluZS4gSWYgeW91J3JlIGxvb2tpbmcgZm9yIGEgZGVlcGVyIGRpdmUsIFtoZXJlJ3MgYSBnb29kIHJldmlldyBhcnRpY2xlXShodHRwczovL2xpbmsuc3ByaW5nZXIuY29tL2FydGljbGUvMTAuMTAwNy9zMTEwNDItMDE4LTY4OTQtNCkgb24gdGhlIG1ldGhvZCBhbmQgcmVjZW50IGlubm92YXRpb25zLg0KDQoNCg0KSGVyZSdzIGFuIGV4YW1wbGUgb2YgY3JlYXRpbmcgYW4gTERBIG1vZGVsIHdpdGgganVzdCAxNSB0b3BpY3MuIA0KDQooSWYgeW91IHdhbnQgcmVwbGljYWJsZSByZXN1bHQsIG1ha2Ugc3VyZSB0byBydW4gYHNldC5zZWVkKFtzb21lIG51bWJlcl0pYCByaWdodCBiZWZvcmUgeW91IHRyYWluIHRoZSB0b3BpYyBtb2RlbC4gV2hpbGUgdG9waWMgbW9kZWxzIHdpbGwgdGhlb3JldGljYWxseSBjb252ZXJnZSB0b3dhcmQgYSBzaW5nbGUgImNvcnJlY3QiIGFuc3dlciwgdGhlIG51bWJlcmluZyBvZiB0aGUgdG9waWNzIGlzIGNvbXBsZXRlbHkgYXJiaXRyYXJ5IGluIHN0YW5kYXJkIExEQSwgc28gdG9waWMgMTAgbWlnaHQgYmUgdG9waWMgMiBuZXh0IHRpbWUgeW91IHJ1biBpdC4pDQoNCmBgYHtyIGxkYX0NCmxpYnJhcnkoc2VlZGVkbGRhKSAjIHBhY2thZ2UgZm9yIGJvdGggc2VlZGVkIExEQSBhbmQgcmVndWxhciBMREENCmxpYnJhcnkoTERBdmlzKSAjIGZvciB2aXN1YWxpemF0aW9uIA0KIyBudW1iZXIgb2YgdG9waWNzOiANCks8LTE1DQoNCiMgdGhlIGluaXRpYWxpemF0aW9uIGlzIHJhbmRvbSwgc28gc2V0IHRoZSByYW5kb20gbnVtYmVyIHNlZWQgZm9yIHJlcGxpY2FiaWxpdHkgDQpzZXQuc2VlZCgxMDApDQpsZGFfbW9kZWw8LXRleHRtb2RlbF9sZGEocG9sX2RmbSwgayA9IEssIA0KICAgICAgICAgICAgICBtYXhfaXRlcj0yMDAwLCANCiAgICAgICAgICAgICAgYXV0b19pdGVyID0gVCwNCiAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICMgcmFpc2luZyB0aGlzIHJlc3VsdCBpbiBtb3JlIHVuaWZvcm0gdGhldGENCiAgICAgICAgICAgICAgYWxwaGEgPSAwLjEsDQogICAgICAgICAgICAgICNyYWlzaW5nIHRoaXMgd2lsbCByZXN1bHQgaW4gYSBtb3JlIHVuaWZvcm0gcGhpDQogICAgICAgICAgICAgIGJldGEgPSAwLjAxDQogICAgICAgICAgICAgIA0KICAgICAgICAgICAgICApDQpgYGANCg0KSWYgeW91IHZpZXcgYGxkYV9tb2RlbCRwaGlgIHlvdSBzaG91bGQgc2VlIGEgbWF0cml4IHdpdGggMTUgcm93cyAob25lIHJvdyBwZXIgdG9waWMpIGFuZCBhcm91bmQgODAwMCBjb2x1bW5zIChvbmUgY29sdW1uIHBlciB0ZXJtKS4gVGhlIG51bWJlciBpbiBlYWNoIHJvdyByZXByZXNlbnQgdGhlIHByb2JhYmlsaXR5IG9mIHRoYXQgdGVybSBmb3IgdGhlIHRvcGljLiAoeW91J2xsIHNvbWV0aW1lcyBzZWUgcGVvcGxlIHJlZmVyIHRvIHRoaXMgbWF0cml4IHVzaW5nIHRoZSBHcmVlayBsZXR0ZXIgJFxwaGkkIG9yIGFzIHRoZSAidG9waWMtd29yZCBkaXN0cmlidXRpb24iKQ0KDQpZb3UgY2FuIHNlZSB0aGUgaGlnaGVzdCBwcm9iYWJpbGl0eSB3b3JkcyBpbiBlYWNoIHRvcGljIHVzaW5nIHRoZSBjb21tYW5kIGJlbG93LCB5b3UnbGwgcHJvYmFibHkgYmUgYWJsZSB0byBwaWNrIG91dCBzb21lIGNvbmNlcHRzIHByZXR0eSBxdWlja2x5DQoNCmBgYHtyfQ0KDQojdG9wIDEwIHRlcm1zIHBlciB0b3BpYw0KdGVybXMobGRhX21vZGVsLCAxMCklPiUNCiAgI2NvbnZlcnRpbmcgdG8gZGF0YSBmcmFtZSAoZm9yIHByZXR0aWVyIGZvcm1hdHRpbmcpDQogIGRhdGEuZnJhbWUoKQ0KDQpgYGANCg0KDQpNZWFud2hpbGUsIHRoZSBgbGRhX21vZGVsJHRoZXRhYCByZXByZXNlbnRzIHRoZSB0b3BpYy13b3JkIGRpc3RyaWJ1dGlvbiBtYXRyaXguIElmIHlvdSB2aWV3IHRoaXMgb2JqZWN0IHlvdSBzaG91bGQgc2VlIG9uZSBjb2x1bW4gcGVyIHRvcGljIGFuZCBvbmUgcm93IHBlciBkb2N1bWVudC4gVGhlIG51bWJlcnMgaW5kaWNhdGUgdGhlIHByb2JhYmlsaXR5IG9mIGEgdG9waWMgaW4gYSBnaXZlbiBkb2N1bWVudC4gKHlvdSdsbCBvZnRlbiBzZWUgdGhpcyBkaXN0cmlidXRpb24gZGVub3RlZCBhcyAkXHRoZXRhJCBvciBhcyB0aGUgImRvY3VtZW50LXRvcGljIG1hdHJpeCIpDQoNCmBgYHtyfQ0KIyBnZXQgdGhlIGRvY3VtZW50LXRvcGljIGRpc3RyaWJ1dGlvbnMgZm9yIHRoZSBmaXJzdCBmZXcgYXJ0aWNsZXM6IA0KaGVhZChsZGFfbW9kZWwkdGhldGEpJT4lDQogIGRhdGEuZnJhbWUoKQ0KDQpgYGANCg0KT25lIHVzZSBvZiB0aGUgdGhlIGRvY3VtZW50IHRvcGljIGRpc3RyaWJ1dGlvbiBpcyBmb3IgaW5mb3JtYXRpb24gcmV0cmlldmFsLiBCYXNlZCBvbiB0aGUgbW9zdCBsaWtlbHkgdGVybXMsIHRvcGljIDEzIGFwcGVhcnMgdG8gYmUgcGlja2luZyB1cCBvbiBzdG9yaWVzIHJlbGF0ZWQgdG8gdGhlIFN1cHJlbWUgQ291cnQncyBkZWNpc2lvbiBvdmVydHVybmluZyBSb2Ugdi4gV2FkZS4gU28gZG9jdW1lbnRzIHdpdGggYSBoaWdoICUgb2YgdGhpcyB0b3BpYyBzaG91bGQgcHJvYmFibHkgYmUgYWJvdXQgdGhhdCBzdG9yeS4gQW5kIGJhc2VkIG9uIHRoZSB1cmxzLCB0aGlzIGludHVpdGlvbiBzZWVtcyBjb3JyZWN0OiANCg0KYGBge3J9DQogICAgI2dldCBkb2N1bWVudCBkYXRhDQpkb2Jic19kb2NzPC1kb2N2YXJzKHBvbF9kZm0pJT4lDQogIA0KICAjIGFkZCAlIG9mIHRvcGljIDEzIHRvIGRvY3VtZW50IGRhdGENCiAgbXV0YXRlKGRvYmJzID0gbGRhX21vZGVsJHRoZXRhWyw0XSklPiUNCiAgIyBzb3J0IGZyb20gbG93ZXN0IHRvIGhpZ2hlc3QgdG9waWMgJQ0KICBhcnJhbmdlKC1kb2JicykNCg0KDQpkb2Jic19kb2NzJT4lDQogIHNlbGVjdCh1cmwpJT4lDQogIHNsaWNlX2hlYWQobj0xMCkNCg0KDQoNCmBgYA0KDQoNCldlIGNhbiB1c2UgdGhpcyBpbnNpZ2h0IHRvIGdldCBzb21lIHNlbnNlIG9mIGhvdyB0aGUgdm9sdW1lIG9mIGNvdmVyYWdlIG9uIHRoaXMgdG9waWMgaGFzIGNoYW5nZWQgb3ZlciB0aW1lLiBIZXJlLCB3ZSdyZSBnZXR0aW5nIHRoZSBtb250aGx5IGF2ZXJhZ2UgJSBvZiB0aGlzIHRvcGljIHdpdGhpbiBlYWNoIGRvY3VtZW50LiBUaGUgYGRvYmJzX3RpbWVsaW5lYCBvYmplY3QganVzdCBhbGxvd3MgdXMgdG8gYWRkIGEgc2V0IG9mIGFubm90YXRpb25zIHRvIHRoZSB0aW1lbGluZS4NCg0KYGBge3IgZG9iYnNwcmVwfQ0KIyBtYWtlIGEgdGltZWxpbmUgb2YgZXZlbnRzIChzbyB3ZSBjYW4gYWRkIGFubm90YXRpb25zIHRvIHRoZSB0aW1lbGluZSkNCmRvYmJzX3RpbWVsaW5lPC1kYXRhLmZyYW1lKGV2ZW50PSBjKCdHaW5zYnVyZyBkZWF0aCcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnQ2VydCBncmFudGVkJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdPcmFsIGFyZ3VtZW50cycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRG9iYnMgbGVhaycsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0RvYmJzIG9waW5pb24nKSwNCiAgICAgICAgICAgICAgICBkYXRlID0gYXMuRGF0ZShjKCIyMDIwLTA5LTE4IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcyMDIxLTA1LTE3JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcyMDIxLTEyLTAxJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcyMDIyLTA1LTAyJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMjAyMi0wNi0yNCcpKQ0KICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICApJT4lDQogIA0KICAjIGNvbnZlcnQgdGltZWxpbmUgdG8gbW9udGhzICh0byBhbGxvdyBqb2luaW5nIHdpdGggbW9udGhseSBjb3ZlcmFnZSBkYXRhKQ0KICBtdXRhdGUobW9udGggPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQ9J21vbnRocycpKQ0KDQptb250aGx5PC1kb2Jic19kb2NzJT4lDQogICMgcm91bmQgZG93biB0byB0aGUgbmVhcmVzdCBtb250aA0KICBtdXRhdGUobW9udGggPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQ9J21vbnRocycpDQogICAgICAgICApJT4lDQogIA0KICAjIGdyb3VwIGJ5IG1vbnRoIGFuZCBjb21wdXRlIHRoZSBtb250aGx5IGF2ZXJhZ2U6IA0KICBncm91cF9ieShtb250aCklPiUNCiAgc3VtbWFyaXNlKGNvdmVyYWdlID0gbWVhbihkb2JicykNCiAgICAgICAgICAgICklPiUNCiAgIyBqb2luIHdpdGggdGltZWxpbmUgZGF0YQ0KICBsZWZ0X2pvaW4oZG9iYnNfdGltZWxpbmUpDQpgYGANCg0KQW5kIGZyb20gaGVyZSwgd2UnbGwgY3JlYXRlIHRoZSBwbG90IGluIEdHcGxvdDogDQoNCmBgYHtyIGRvYmJzcGxvdH0NCiMgY3JlYXRlIHBsb3Q6IA0KbW9udGhseSU+JQ0KICBnZ3Bsb3QoYWVzKHg9bW9udGgsIHk9Y292ZXJhZ2UsIGxhYmVsPWV2ZW50KSkgKyANCiAgIyBjcmVhdGUgdGhlIGxpbmUgcGxvdA0KICBnZW9tX2xpbmUoKSArDQogICMgYWRkIHBvaW50cyBmb3IgZWFjaCBtb250aA0KICBnZW9tX3BvaW50KCkgKw0KICAjIHR1cm4gb2ZmIGNsaXBwaW5nIChhdm9pZHMgcmVtb3ZpbmcgbGFiZWxzIGlmIHRoZXkncmUgb3V0c2lkZSB0aGUgcGxvdCBhcmVhKQ0KICBjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICJvZmYiKSArIA0KICANCiAgIyBhZGQgbGFiZWxzICh2anVzdCAtMSBzZXRzIGFubm90YXRpb24ganVzdCBhYm92ZSB0aGUgdGltZWxpbmUpDQogIGdlb21fbGFiZWwodmp1c3Q9LTEpICsNCiAgeWxhYignQXZlcmFnZSB0b3BpYyAlJykgKw0KICAjIHVzaW5nIHRoZSBtaW5pbWFsIHRoZW1lOiANCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgZ2d0aXRsZSgnQXZlcmFnZSBwZXJjZW50IHRvcGljIDEzIChSb2Ugb3ZlcnR1cm5lZCknKSAgKw0KICAjIHNob3cgdGlja3MgZXZlcnkgMyBtb250aHMNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcz0iMyBtb250aCIsIGRhdGVfbGFiZWxzID0gIiVPYiAnJXkiKSArDQogICMgbGFiZWxzIGF0IDQ1IGRlZ3JlZSBhbmdsZQ0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkNCmBgYA0KDQoNCkxhc3RseSwgeW91IGNhbiB1c2UgdGhlIFtMREF2aXMgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2Nwc2lldmVydC9MREF2aXMpIHRvIHZpc3VhbGl6ZSBhbmQgaW50ZXJwcmV0IHlvdXIgdG9waWNzLiANCg0KVGhlIGxlZnQgcGFuZWwgdXNlcyBhIFtwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9QcmluY2lwYWxfY29tcG9uZW50X2FuYWx5c2lzKSB0byBtb2RlbCB0aGUgImRpc3RhbmNlIiBiZXR3ZWVuIGVhY2ggdG9waWMuIFRvcGljcyB0aGF0IGFyZSBmdXJ0aGVyIGFwYXJ0IGFyZSBsZXNzIHNpbWlsYXIgdG8gb25lIGFub3RoZXIsIGFuZCBsYXJnZXIgYnViYmxlcyBpbmRpY2F0ZSB0aGUgdG9waWMgaXMgbW9yZSBjb21tb24uIFNlbGVjdGluZyBhIHRvcGljIHdpbGwgY2F1c2UgdGhlIHJpZ2h0IHBhbmVsIHRvIGRpc3BsYXkgcmVsZXZhbnQgdGVybXMgZm9yIHRoYXQgdG9waWMuIE5vdGU6IGNsaWNraW5nIHRoZSBsaW5rcyBpbiB0aGUgYm90dG9tIHJpZ2h0IHdpbGwgYnJpbmcgdXAgbW9yZSBkZXRhaWxlZCBpbmZvcm1hdGlvbiBvbiB3aGF0IHRoZXNlIG1ldHJpY3MgbWVhbiwgYnV0IHRoZSBzaG9ydCB2ZXJzaW9uIGlzIHRoYXQgdGhleSdyZSBib3RoIG1lYW50IHRvIG1ha2UgaXQgZWFzaWVyIHRvIGlkZW50aWZ5IHRoZSBtb3N0IGRpc3RpbmN0aXZlIHRlcm1zIGZvciBlYWNoIHRvcGljLCByYXRoZXIgdGhhbiB0aGUgdGVybXMgdGhhdCBoYXZlIHRoZSBoaWdoZXN0IGZyZXF1ZW5jeS4gDQoNCkluIG9yZGVyIHRvIChob3BlZnVsbHkpIHNpbXBsaWZ5IHRoZSBwcm9jZXNzIG9mIHNldHVwLCB0aGVyZSdzIGEgY3VzdG9tIGZ1bmN0aW9uIGluY2x1ZGVkIGluIHRoZSBoZWFkZXIgb2YgdGhpcyBub3RlYm9vayB0aGF0IHdpbGwgYWxsb3cgeW91IHRvIHNhdmUgYW5kIGVtYmVkIHRoZXNlIHZpc3VhbGl6YXRpb25zLiBIZXJlJ3MgdGhlIGNvZGUgeW91IHdvdWxkIHVzZSB0byBtYWtlIHRoaXMgZGlzcGxheSBvbiB5b3VyIGxvY2FsIGRldmljZToNCg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgYSBmaWxlbmFtZSB0byBzYXZlIHRoZSBwbG90IHVuZGVyDQptb2RlbHZpczwtJ2xkYV92aXMuaHRtbCcNCiMgY3JlYXRlIHRoZSB2aXN1YWxpemF0aW9uDQpMREF2aXNfc3RhbmRhbG9uZShsZGFfbW9kZWwsIG91dHB1dGZpbGU9bW9kZWx2aXMpDQojIGJyaW5nIHVwIGEgd2luZG93IHdpdGggdGhlIHZpc3VhbGl6YXRpb246IA0KYnJvd3NlVVJMKG1vZGVsdmlzKQ0KDQpgYGANCg0KWW91IGNvdWxkIHRha2Ugc2NyZWVuc2hvdHMgb2YgdGhpcyB2aXN1YWxpemF0aW9uIGZvciBkaWZmZXJlbnQgdG9waWNzIGluIG9yZGVyIHRvIHNoYXJlIGl0LCBidXQgaWYgeW91IHdhbnRlZCB0byBzaGFyZSB0aGUgYWN0dWFsIGludGVyYWN0aXZlIHZlcnNpb24gb2YgdGhpcyB2aXN1YWxpemF0aW9uIGluIGFuIFJtYXJrZG93biBmaWxlIG9yIG5vdGVib29rLCB5b3UgY291bGQgZG8gaXQgdXNpbmcgdGhlIGBpbmNsdWRlSFRNTGAgZnVuY3Rpb24gZnJvbSB0aGUgaHRtbHRvb2xzIHBhY2thZ2UuIA0KDQpgYGB7ciBsZGF2aXMsIHJlc3VsdHM9J2FzaXMnfQ0KbGRhX3ZpczwtJ2xkYV92aXN1YWwuaHRtbCcNCkxEQXZpc19zdGFuZGFsb25lKGxkYV9tb2RlbCwgb3V0cHV0ZmlsZT1sZGFfdmlzKQ0KaHRtbHRvb2xzOjppbmNsdWRlSFRNTChsZGFfdmlzKQ0KYGBgDQoNCiMjIFNlZWRlZCBMREEgDQoNCkluIHNlZWRlZCBMREEsIHlvdSBzdXBwbHkgc29tZSBpbml0aWFsIHRlcm1zIGFuZCB0b3BpY3MsIGFuZCB0aGUgbW9kZWwgd2lsbCBpbmZlciBhZGRpdGlvbmFsIHRlcm1zIGJhc2VkIG9uIHlvdXIgd29yZHMuIA0KDQpIZXJlIEkndmUgY3JlYXRlZCBhIGN1c3RvbSBkaWN0aW9uYXJ5IHdpdGggMTAgdG9waWNzIGFuZCBqdXN0IGEgaGFuZGZ1bCBvZiB0ZXJtcyBmb3IgZWFjaC4gTm90aWNlIHRoYXQgdGVybXMgd2l0aCBgKmAgd2lsbCBiZSBhYmxlIHRvIG1hdGNoIGRpZmZlcmVudCBlbmRpbmdzLCBzbyBgdmFjY2luKmAgc2hvdWxkIGJlIGFibGUgdG8gbWF0Y2ggYW55IG9mIHZhY2NpbmF0ZSwgdmFjY2luZSwgYW5kIHZhY2NpbmF0aW9uLiANCg0KYGBge3Igc2VlZGVkX2xkYX0NCm5ld3NfZGljdDwtbGlzdCgnY292aWQnID0gYygnY292aWQqJywgJ3ZhY2NpbionLCAnbG9ja2Rvd24qJyksDQogICAgICAgICAgICAgICAgJ25hdHNlYycgPSBjKCdhZmdoYW4qJywgJ3RhbGliYW4nLCAncHV0aW4nLCAnY2hpbmEnLCAndWtyYWluKicsJ3plbGVuc2t5JywgJ3RlcnJvcionKSwNCg0KICAgICAgICAgICAgICAgICdlbGVjdGlvbnMnID0gYygnZWxlY3Rpb24nLCAnY2F1Y3VzJywgJ3ByaW1hcnknLCAnZGViYXRlKicsICdub21pbmF0aW9uJywgJ21pZHRlcm0nLCAndm90ZSonLCAncG9sbConKSwNCiAgICAgICAgICAgICAgICAndHJ1bXAnID0gYygnaW5kaWN0bWVudCcsICdpbXBlYWNobWVudCcsICdtYXItYS1sYWdvJyksDQogICAgICAgICAgICAgICAgJ2phbjYnID0gYygiY2FwaXRvbCIsICdqYW51YXJ5JywgJ2NoYW5zbGV5JywnZnJhdWQnKSwNCiAgICAgICAgICAgICAgICAnYmxtJyAgPSBjKCdmbG95ZCcgLCAnYmxhY2snLCAncG9saWNlJywgInByb3Rlc3QqIiksDQogICAgICAgICAgICAgICAgJ2ltbWlncmF0aW9uJyA9IGMoJ2ltbWlncmF0KicsICAnYm9yZGVyJywgJ2hvbWVsYW5kJywgJ21pZ3JhbnQqJyksDQogICAgICAgICAgICAgICAgJ2NsaW1hdGUnID0gYygnZ2xvYmFsJywnY2xpbWF0ZSonLCdmdWVsJywgJ2NhcmJvbicsICdlbWlzc2lvbionKSwNCiAgICAgICAgICAgICAgICAnYWJvcnRpb24nID0gYygnYWJvcnQqJywgICdyb2UnLCAnd2FkZScsICdmZXR1cycsICdkb2JicycpLA0KICAgICAgICAgICAgICAgICdlY29ub215JyA9IGMoJ2luZmxhdGlvbicsJ3ByaWNlKicsICd0YXgnKQ0KICAgICAgICAgICAgICAgICkNCiMgY29udmVyIHRvIGEgcXVhbnRlZGEgZGljdGlvbmFyeQ0KZGljdF90b3BpYzwtZGljdGlvbmFyeShuZXdzX2RpY3QpDQpzZXQuc2VlZCgxMDApICMgc2V0dGluZyByYW5kb20gbnVtYmVyIHNlZWQNCnNsZGFfbW9kZWwgPC0gdGV4dG1vZGVsX3NlZWRlZGxkYShwb2xfZGZtLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpY3Rpb25hcnkgPSBkaWN0X3RvcGljLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9faXRlciA9IFQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzaWR1YWwgPSA1LCAjIDUgcmVzaWR1YWwgdG9waWNzIGZvciBnYXJiYWdlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfaXRlcj0yMDAwKQ0KYGBgDQoNCkFnYWluLCB3ZSBjYW4gZXh0cmFjdCB0b3BpY3MgZnJvbSB0aGlzIG1vZGVsIHVzaW5nIHRoZSBgdGVybXNgIGZ1bmN0aW9uLiBZb3UnbGwgbm90aWNlIHRoYXQgdGhlcmUgYXJlIHRvcGljcyB0aGF0IGNvcnJlc3BvbmQgdG8gdGhlIGNhdGVnb3JpZXMgaW4gdGhlIGRpY3Rpb25hcnksIGJ1dCB0aGVyZSBhcmUgbG90cyBvZiB3b3JkcyB0aGF0IHdlcmVuJ3QgaW5pdGlhbGx5IGdpdmVuIHRvIHRoZSBtb2RlbC4gT25lIHVzZWZ1bCBhcHBsaWNhdGlvbiBvZiB0aGlzIGFwcHJvYWNoIGlzIGl0IGNhbiBoZWxwIHlvdSBjb21lIHVwIGFkZGl0aW9uYWwgdGVybXMgd2hlbiBjb25zdHJ1Y3RpbmcgYSBkaWN0aW9uYXJ5LiANCg0KDQpgYGB7ciBzbGRhdGVybXN9DQp0ZXJtcyhzbGRhX21vZGVsKSU+JQ0KICAjY29udmVydGluZyB0byBhIGRhdGEgZnJhbWUgKGp1c3QgdG8gbWFrZSBpdCBsb29rIG5pY2VyIGluIHRoZSBub3RlYm9vaykNCiAgZGF0YS5mcmFtZSgpDQpgYGANCg0KDQpBbmQsIGp1c3QgbGlrZSB3aXRoIHJlZ3VsYXIgTERBLCB3ZSBjYW4gdXNlIHRoZSBpbmZlcnJlZCB0b3BpY3MgdG8gZG8gdGhpbmdzIGxpa2UgdHJhY2sgbmV3cyBjb3ZlcmFnZSBvdmVyIHRpbWUgKG5vdGUgdGhhdCB0aGUgY29sdW1ucyBub3cgaGF2ZSBuYW1lcyBiYXNlZCBvbiBvdXIgZGljdGlvbmFyeSkgSGVyZSdzIGhvdyBjb3ZlcmFnZSBvZiBDb3ZpZC0xOSB0b3BpYyBzdGVhZGlseSBkcm9wcGVkIHNpbmNlIDIwMjAuIA0KDQoNCmBgYHtyIGNvdmlkcGxvdH0NCg0KY292aWRfZGY8LQ0KICBkb2N2YXJzKHBvbF9kZm0pJT4lDQogICMgYWRkICUgb2YgY292aWQgdG9waWMNCiAgbXV0YXRlKGNvdmlkID0gc2xkYV9tb2RlbCR0aGV0YVssJ2NvdmlkJ10sDQogICAgICAgICAgICMgcm91bmQgZG93biB0byB0aGUgbmVhcmVzdCBtb250aA0KICAgICAgICAgbW9udGggPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQ9J21vbnRocycpKSU+JQ0KICAjIGdyb3VwIGJ5IG1vbnRoIGFuZCBjb21wdXRlIHRoZSBtb250aGx5IGF2ZXJhZ2U6IA0KICBncm91cF9ieShtb250aCklPiUNCiAgc3VtbWFyaXNlKGNvdmlkID0gbWVhbihjb3ZpZCkpDQpjb3ZpZF9kZiU+JQ0KICBnZ3Bsb3QoYWVzKHg9bW9udGgsIHk9Y292aWQpKSArIA0KICAjIGNyZWF0ZSB0aGUgbGluZSBwbG90DQogIGdlb21fbGluZSgpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgI2FkZCBhIG1hcmtlciBmb3IgdGhlIHN0YXJ0IG9mIEZsb3lkIHByb3Rlc3RzOg0KDQogIHlsYWIoJ0F2ZXJhZ2UgdG9waWMgJScpICsNCiAgIyB1c2luZyB0aGUgbWluaW1hbCB0aGVtZTogDQogIHRoZW1lX21pbmltYWwoKSArDQogIGdndGl0bGUoJ0F2ZXJhZ2UgJSBjb3ZpZCB0b3BpYyBieSBtb250aCcpICsNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcz0iMyBtb250aCIsIGRhdGVfbGFiZWxzID0gIiVPYiAnJXkiKSArDQogICMgbGFiZWxzIGF0IDQ1IGRlZ3JlZSBhbmdsZQ0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkNCiAgDQpgYGANCg0KDQpMYXN0bHksIHRoZSBzYW1lIG1ldGhvZCB3ZSB1c2VkIGZvciBMREFWaXMgYWJvdmUgc2hvdWxkIHdvcmsgaGVyZSAodW5mb3J0dW5hdGVseSB0aGlzIGRvZXNuJ3QgcGljayB1cCBvbiB0aGUgdG9waWMgbmFtZXMpDQoNCg0KYGBge3Igc2xkYXZpcywgcmVzdWx0PSdhc2lzJ30NCnNsZGFfdmlzPC0nc2xkYV92aXMuaHRtbCcNCkxEQXZpc19zdGFuZGFsb25lKHNsZGFfbW9kZWwsIG91dHB1dGZpbGU9c2xkYV92aXMpDQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKHNsZGFfdmlzKQ0KYGBgDQoNCiMjIyBTdHJ1Y3R1cmFsIFRvcGljIE1vZGVscw0KDQpVbmxpa2UgdHJhZGl0aW9uYWwgTERBLCBTdHJ1Y3R1cmFsIFRvcGljIE1vZGVscyBbKHBhcGVyKV0oaHR0cHM6Ly9zY2hvbGFyLmhhcnZhcmQuZWR1L2ZpbGVzL2R0aW5nbGV5L2ZpbGVzL3RvcGljbW9kZWxzb3BlbmVuZGVkZXhwZXJpbWVudHMucGRmKSBhbGxvdyB1c2VycyB0byBpbmNsdWRlIGFkZGl0aW9uYWwgZG9jdW1lbnQtbGV2ZWwgZGF0YSB3aGVuIGluZmVycmluZyB0aGVpciB0b3BpY3MuIFRoZSAicHJldmFsZW5jZSBtb2RlbCIgd2lsbCBhbGxvdyB0b3BpYyBwcm9iYWJpbGl0aWVzIHRvIHZhcnkgc3lzdGVtYXRpY2FsbHkgYWNjb3JkaW5nIHRvIGEgY2hhcmFjdGVyaXN0aWMgb2YgdGhlIGRvY3VtZW50IChzdWNoIGFzIHRoZSBhdXRob3Igb3Igc291cmNlKSwgdGhlICJjb250ZW50IG1vZGVsIiB3aWxsIGFsbG93IHRoZSB0ZXJtLXByb2JhYmlsaXRpZXMgd2l0aGluIGVhY2ggdG9waWMgdG8gdmFyeSBpbiB0aGUgc2FtZSB3YXkuIEZvciBpbnN0YW5jZTogaWYgd2UgdGhpbmsgdGhhdCBkaWZmZXJlbnQgc291cmNlcyBhcmUgbW9yZSBsaWtlbHkgdG8gdGFsayBhYm91dCBjZXJ0YWluIGlzc3Vlcywgd2UgY2FuIGV4cGxpY2l0bHkgbW9kZWwgdGhhdCBpbmZvcm1hdGlvbiBhcyBwYXJ0IG9mIHRoZSBpbmZlcmVuY2UgcHJvY2Vzcy4gSGVyZSwgd2UncmUgdXNpbmcgdGhlIHNvdXJjZSB2YXJpYWJsZSAoRm94IE5ld3MgdnMuIENOTikgYXMgYSBwcmVkaWN0b3IgZm9yIHRoZSB0b3BpYyBwcm9iYWJpbGl0aWVzLiBUaGUgYXNzdW1wdGlvbiAoYSByZWFzb25hYmx5IHNhZmUgb25lKSBpcyB0aGF0IHRoZSB0d28gb3V0bGV0cyB3aWxsIGRpZmZlciBzeXN0ZW1hdGljYWxseSBpbiB0aGUgc29ydHMgb2YgbmV3cyB0aGV5IGNvdmVyLiANCg0KYGBge3J9DQpsaWJyYXJ5KHN0bSkgIyBmb3Igc3RydWN0dXJhbCB0b3BpYyBtb2RlbHMNCiMgY29udmVydCBERk0gdG8gYSBmb3JtYXQgdXNhYmxlIGJ5IFNUTQ0KZGZtMnN0bSA8LSBjb252ZXJ0KHBvbF9kZm0sIHRvID0gInN0bSIpDQojIG51bWJlciBvZiB0b3BpY3MNCks8LTE1DQojIHJhbmRvbSBudW1iZXIgc2VlZCANCnNldC5zZWVkKDEwMDApDQojIHJ1biB0aGUgbW9kZWwNCnN0bV9tb2RlbCA8LSBzdG0oZGZtMnN0bSRkb2N1bWVudHMsIGRmbTJzdG0kdm9jYWIsIA0KICAgICAgICAgICAgICAgICBLID0gSywgDQogICAgICAgICAgICAgICAgIGRhdGEgPSBkZm0yc3RtJG1ldGEsIA0KICAgICAgICAgICAgICAgICAjIGluY2x1ZGUgc291cmNlIGluIHRoZSBwcmV2YWxlbmNlIG1vZGVsDQogICAgICAgICAgICAgICAgIHByZXZhbGVuY2UgPSAgfiBzb3VyY2UsDQogICAgICAgICAgICAgICAgIGluaXQudHlwZSA9ICJTcGVjdHJhbCIsDQogICAgICAgICAgICAgICAgICMgc2V0IHZlcmJvc2UgPVQgaWYgeW91IHdhbnQgdG8gd2F0Y2ggdGhlIG1vZGVsIHByb2dyZXNzDQogICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGIA0KICAgICAgICAgICAgICAgICApDQoNCg0KYGBgDQoNCg0KVGhlIFNUTSBwYWNrYWdlIGluY2x1ZGVzIGEgc3BlY2lhbCBgbGFiZWxUb3BpY3NgIGZ1bmN0aW9uIHRvIHJldHJpZXZlIHRoZSB0ZXJtcyBhc3NvY2lhdGVkIHdpdGggZWFjaCB0b3BpYy4gV2hpbGUgdGhlIGhpZ2hlc3QgcHJvYmFiaWxpdHkgdGVybXMgY2FuIGJlIGEgZ29vZCB3YXkgdG8gaWRlbnRpZnkgdG9waWNzLCBpdCBjYW4gYmUgc29tZXdoYXQgdW5oZWxwZnVsIGJlY2F1c2Ugc29tZXRpbWVzIHRlcm1zIHNob3cgdXAgYXQgdGhlIHRvcGljIG9mIHRoZSBsaXN0IGJlY2F1c2UgdGhleSdyZSBqdXN0IGNvbW1vbiB3b3JkcyBpbiBnZW5lcmFsLiBUaGUgRlJFWCwgbGlmdCwgYW5kIHNjb3JlIG1ldHJpY3MgYWxsIGF0dGVtcHQgdG8gYWNjb3VudCBmb3IgdGVybSBmcmVxdWVuY3kgaW4gc29tZSB3YXkgaW4gb3JkZXIgdG8gZ2l2ZSBncmVhdGVyIHdlaWdodCB0byB0ZXJtcyB0aGF0IGFyZSBtb3JlIGRpc3RpbmN0IHRvIGVhY2ggdG9waWMuIA0KDQpgYGB7cn0NCg0KbGFiZWxUb3BpY3Moc3RtX21vZGVsLCB0b3BpY3M9MTo1LCBuPTUpICMgbm90ZSAtIG9ubHkgc2hvd2luZyBmaXJzdCA1IG9mIDE1IHRvcGljcyBoZXJlDQoNCmBgYA0KDQpZb3UgY2FuIHZpZXcgYHN0bV9tb2RlbCR0aGV0YWAgdG8gZ2V0IHRoZSBkb2N1bWVudC10b3BpYyBwcm9iYWJpbGl0aWVzLiBCdXQgU1RNIGFsc28gaW5jbHVkZXMgdGhlIGBmaW5kVGhvdWdodHNgIGNvbW1hbmQgdG8gaGVscCBpZGVudGlmeSBkb2N1bWVudHMgdGhhdCBhcmUgcmVwcmVzZW50YXRpdmUgb2YgYSBnaXZlbiB0b3BpYy4gSGVyZSwgSSdtIGdldHRpbmcgYSBkb2N1bWVudCB0aGF0IGlzIHJlcHJlc2VudGF0aXZlIG9mIHRvcGljIDYsIHdoaWNoIGFwcGVhcnMgdG8gYmUgcmVsYXRlZCB0byBpbW1pZ3JhdGlvbiBhbmQgYm9yZGVyIGNvbnRyb2wgaXNzdWVzIA0KYGBge3IsUi5vcHRpb25zPWxpc3QobWF4LnByaW50PTEwKX0NCmZpbmRUaG91Z2h0cyhzdG1fbW9kZWwsIA0KICAgICAgICAgICAgICMgbGlzdCBvZiB0ZXh0cyAodGhlIGRpbW5hbWVzIHBhcnQgaGVyZSBlbnN1cmVzIHRoYXQgdGhlIGRvYyBuYW1lcyBsaW5lIHVwIHdpdGggdGhlIG5hbWVzIGluIHRoZSBERk0pDQogICAgICAgICAgICAgdGV4dHMgPSBwb2xfY29ycHVzW3BvbF9kZm1ARGltbmFtZXNbWzFdXV0sIA0KICAgICAgICAgICAgICMgdG9waWMgNg0KICAgICAgICAgICAgIHRvcGljcyA9IDYgLA0KICAgICAgICAgICAgIG4gPSAxKQ0KYGBgDQoNCg0KVGhlIG1vcmUgaW1wb3J0YW50IGlubm92YXRpb24gaGVyZSBpcyB0aGF0IHdlIGNhbiBlc3RpbWF0ZSB0aGUgZWZmZWN0IG9mIGEgY292YXJpYXRlIG9uIHRoZSB0b3BpYyBwcmV2YWxlbmNlLiBIZXJlLCBJJ20gZXN0aW1hdGluZyB0aGUgZWZmZWN0IG9mIHNvdXJjZSBvbiB0aGUgcHJldmFsZW5jZSBvZiBhbGwgMTUgdG9waWNzIChhbHRob3VnaCBJJ20gb25seSBzaG93aW5nIHRoZSByZXN1bHRzIGZvciB0b3BpYyA2KQ0KDQpgYGB7cn0NCmZ4IDwtIGVzdGltYXRlRWZmZWN0KDE6MTUgfiBzb3VyY2UgLCBzdG1fbW9kZWwsDQogICAgICAgICAgICAgICAgICAgICAgICBtZXRhID0gZGZtMnN0bSRtZXRhKQ0KIyBvbmx5IHNob3dpbmcgdG9waWMgNiAocmVtb3ZlIHRoZSAkdGFibGVzW1s2XV0gcGFydCB0byBzZWUgYWxsIHRvcGljcykNCnN1bW1hcnkoZngpJHRhYmxlc1tbNl1dDQoNCmBgYA0KDQpXZSBjYW4gYWxzbyBwbG90IHRoZSByZXN1bHRzIChhbHRob3VnaCB0aGlzIGlzIHZlcnkgbWVzc3kgZm9yIG1vcmUgdGhhbiBhIGhhbmRmdWwgb2YgdG9waWNzKS4gSW4gZWl0aGVyIGNhc2UsIHdlIHNlZSBGb3ggaXMgZ2VuZXJhbGx5IG1vcmUgbGlrZWx5IHRvIGNvdmVyIHRoZSBpbW1pZ3JhdGlvbiB0b3BpYyBjb21wYXJlZCB0byBDTk4uDQoNCmBgYHtyfQ0KcGxvdC5lc3RpbWF0ZUVmZmVjdChmeCwgDQogICAgICAgICAgICAgICAgICAgIGNvdmFyaWF0ZSA9ICJzb3VyY2UiLCANCiAgICAgICAgICAgICAgICAgICAgdG9waWNzID0gMTo2LA0KICAgICAgICAgICAgICAgICAgICAjIHZhbHVlIG9mIGZveCBuZXdzIC0gY25uDQogICAgICAgICAgICAgICAgICAgIGNvdi52YWx1ZTEgPSAiRm94IE5ld3MiLA0KICAgICAgICAgICAgICAgICAgICBjb3YudmFsdWUyICA9ICJDTk4iLA0KICAgICAgICAgICAgICAgICAgICBtZXRob2Q9J2RpZmZlcmVuY2UnLA0KICAgICAgICAgICAgICAgICAgICAjIHlvdSdsbCBuZWVkIHRvIGFkanVzdCB0aGlzIHRvIGtlZXAgdGhlIHRleHQgZnJvbSBnZXR0aW5nIGN1dCBvZmYgDQogICAgICAgICAgICAgICAgICB4bGltICA9IGMoLS4xLCAuMSkNCiAgICAgICAgICAgICAgICAgICAgKQ0KYGBgDQoNClRoZXJlJ3MgYWxzbyBhIChzbGlnaHRseSBqYW5reSkgcGFja2FnZSBjYWxsZWQgW3N0bWluc2lnaHRzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvc3RtaW5zaWdodHMvdmlnbmV0dGVzL2ludHJvLmh0bWwpIHRoYXQgd2lsbCBhbGxvdyB5b3UgdG8gZG8gc29tZSBvZiB0aGlzIGludGVyYWN0aXZlbHkuIFRoaXMgaXNuJ3Qgc29tZXRoaW5nIHlvdSBjYW4gcmVhbGx5IHNoYXJlIHVubGVzcyB5b3UgdXBsb2FkIGl0IHRvIGEgc2VydmVyIHNvbWV3aGVyZSwgYnV0IHlvdSBjYW4gdXNlIGl0IHRvIGdlbmVyYXRlIGFuZCB0aGVuIGRvd25sb2FkIHNvbWUgcGxvdHMgd2hpY2ggeW91IGNvdWxkIHVzZSBlbHNld2hlcmUuIA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgZm9yIHNvbWUgcmVhc29uLCB0aGUgZGZtIGRhdGEgbXVzdCBiZSBuYW1lZCAib3V0IiBvciBlbHNlIHRoaXMgZG9lc24ndCB3b3JrLiANCm91dDwtZGZtMnN0bQ0KIyBzYXZlIHRoZSByZWxldmFudCBtb2RlbCBvdXRwdXRzOiANCnNhdmUoZmlsZT0nc3RtX21vZGVsLlJEYXRhJywgbGlzdCA9IGMoJ3N0bV9tb2RlbCcsICdvdXQnLCAnZngnLCAncG9sX2NvcnB1cycpKQ0KbGlicmFyeShzdG1pbnNpZ2h0cykNCiMgZ28gdG8gYnJvd3NlIGFuZCBmaW5kIHN0bV9tb2RlbC5SZGF0YSB0byBnZXQgdGhpcyBydW5uaW5nIA0KcnVuX3N0bWluc2lnaHRzKCkNCmBgYA0KDQoNCiMjIEtleXdvcmQgQXNzaXN0ZWQgVG9waWMgTW9kZWxzDQoNCktleXdvcmQgQXNzaXN0ZWQgVG9waWMgbW9kZWxzIFsoc2VlOiBwYXBlcildKGh0dHBzOi8vaW1haS5mYXMuaGFydmFyZC5lZHUvcmVzZWFyY2gvZmlsZXMva2V5QVRNLnBkZikgYXJlIGEgbmV3IGFwcHJvYWNoIHRoYXQgYWxsb3dzIGJvdGggZG9jdW1lbnQgY292YXJpYXRlcyBhbmQgc2VlZCB0ZXJtcyB0byBkZWZpbmUgdG9waWNzLiBJbiBzaG9ydDogaXQgY29tYmluZXMgdGhlIFNlZWRlZExEQSBhcHByb2FjaCB3aXRoIHRoZSBTdHJ1Y3R1cmFsIFRvcGljIE1vZGVsLiBUaGlzIGlzIGEgcHJldHR5IG5ldyBtZXRob2QgYW5kIHRoZSBwYWNrYWdlIGlzIHN0aWxsIHVuZGVyIGRldmVsb3BtZW50LCBidXQgSSdtIGluY2x1ZGluZyBpdCBoZXJlIGJlY2F1c2UgaXQgbWlnaHQgYmUgb2YgaW50ZXJlc3QgZm9yIHRob3NlIG9mIHlvdSB3aG8gYXJlIGFscmVhZHkgdXNpbmcgZGljdGlvbmFyeSBiYXNlZCBtZXRob2RzLiANCg0KVGhlIHNldHVwIGZvciB0aGUgYmFzaWMgbW9kZWwgaXMgYWxtb3N0IGlkZW50aWNhbCB0byBzZWVkZWQgTERBLCB0aGUgbWFpbiBwcmFjdGljYWwgZGlmZmVyZW5jZSBpcyB0aGF0IGl0IGF1dG9tYXRpY2FsbHkgaW5mZXJzIHRoZSAkXGFscGhhJCBhbmQgJFxiZXRhJCBwYXJhbWV0ZXJzIHRoYXQgY29udHJvbCBob3cgZGlmZnVzZSB0aGUgJFx0aGV0YSQgYW5kICRccGhpJCBhcmUsIHdoaWNoIGNhbiBzYXZlIHlvdSB0aGUgZWZmb3J0IG9mIGhhdmluZyB0byB0dW5lIHRoZXNlIHBhcmFtZXRlcnMuIA0KDQpXZSBjYW4gdXNlIHRoZSBzYW1lIGRpY3Rpb25hcnkgd2UgaW1wbGVtZW50ZWQgYmVmb3JlLiBUaGUga2V5QVRNIHBhY2thZ2UgYWxzbyBwcm92aWRlcyBzb21lIGFkZGl0aW9uYWwgb3B0aW9ucyBmb3IgY2hlY2tpbmcgdGhlIHF1YWxpdHkgb2Ygb3VyIHNlbGVjdGVkIGtleXdvcmQgLSBub3RpY2UgdGhhdCBpdCB0aHJvd3MgYSB3YXJuaW5nIGJlY2F1c2Ugc29tZSBvZiB0aGUgdGVybXMgaW4gb3VyIGtleXdvcmQgbGlzdCBkb24ndCBhY3R1YWxseSBhcHBlYXIgaW4gdGhlIGNvcnB1cy4gU29tZSB0ZXJtcyB3ZXJlIHByb2JhYmx5IHRvbyBjb21tb24gYW5kIGdvdCByZW1vdmVkIHdoZW4gd2UgcmFuIGBkZm1fdHJpbWAsIG90aGVyIHRlcm1zIG1heSBqdXN0IGJlIHRvbyByYXJlIHRvIGJlIHVzZWZ1bC4gDQoNCkFsdGhvdWdoIGl0IHdpbGwgbWFrZSB0aGUgbW9kZWwgcnVuIHNsb3dlciwgeW91IG1pZ2h0IHdhbnQgdG8gaW5jcmVhc2UgIGBtYXhfZG9jZnJlcWAgd2hlbiB5b3UgY2xlYW4gdGhlIGRhdGEuIA0KDQpgYGB7cn0NCmxpYnJhcnkoa2V5QVRNKSAjIGtleXdvcmQgYXNzaXN0ZWQgdG9waWMgbW9kZWxzDQojIG91ciBuZXdzIGRpY3Rpb25hcnkNCm5ld3NfZGljdDwtbGlzdCgnY292aWQnID0gYygnY292aWQqJywgJ3ZhY2NpbionLCAnbG9ja2Rvd24qJyksDQogICAgICAgICAgICAgICAgJ25hdHNlYycgPSBjKCdhZmdoYW4qJywgJ3RhbGliYW4nLCAncHV0aW4nLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2NoaW5hJywgJ3VrcmFpbionLCd6ZWxlbnNreScsICd0ZXJyb3IqJyksDQogICAgICAgICAgICAgICAgJ2VsZWN0aW9ucycgPSBjKCdlbGVjdGlvbicsICdjYXVjdXMnLCAncHJpbWFyeScsICdkZWJhdGUqJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdub21pbmF0aW9uJywgJ21pZHRlcm0nLCAndm90ZSonLCAncG9sbConKSwNCiAgICAgICAgICAgICAgICAndHJ1bXAnID0gYygnaW5kaWN0bWVudCcsICdpbXBlYWNobWVudCcsICdtYXItYS1sYWdvJyksDQogICAgICAgICAgICAgICAgJ2phbjYnID0gYygiY2FwaXRvbCIsICdqYW51YXJ5JywgJ2NoYW5zbGV5JywnZnJhdWQnKSwNCiAgICAgICAgICAgICAgICAnYmxtJyAgPSBjKCdmbG95ZCcgLCAnYmxhY2snLCAncG9saWNlJywgInByb3Rlc3QqIiwgJ2JsbScsJ2JsYWNrLWxpdmVzLW1hdHRlcicpLA0KICAgICAgICAgICAgICAgICdpbW1pZ3JhdGlvbicgPSBjKCdpbW1pZ3JhdConLCAgJ2JvcmRlcicsICdob21lbGFuZCcsICdtaWdyYW50KicpLA0KICAgICAgICAgICAgICAgICdjbGltYXRlJyA9IGMoJ2dsb2JhbCcsJ2NsaW1hdGUqJywnZnVlbCcsICdjYXJib24nLCAnZW1pc3Npb24qJyksDQogICAgICAgICAgICAgICAgJ2Fib3J0aW9uJyA9IGMoJ2Fib3J0KicsICAncm9lJywgJ3dhZGUnLCAnZmV0dXMnLCAnZG9iYnMnKSwNCiAgICAgICAgICAgICAgICAnZWNvbm9teScgPSBjKCdpbmZsYXRpb24nLCdwcmljZSonLCAndGF4JykNCiAgICAgICAgICAgICAgICApDQoNCiMgY29udmVydGVkIHRvIGEgcXVhbnRlZGEgZGljdGlvbmFyeQ0KZGljdF90b3BpYzwtZGljdGlvbmFyeShuZXdzX2RpY3QpDQojIGNvbnZlcnQgdGhlIHBvbF9kZm0gdG8gYSBzdHJ1Y3R1cmUgcHJlZmVycmVkIGJ5IHRoaXMgcGFja2FnZQ0Ka2V5QVRNX2RvY3M8LWtleUFUTV9yZWFkKHBvbF9kZm0pDQojIGNvbnZlcnQgdGhlIHF1YW50ZWRhIGRpY3Rpb25hcnkgdG8gYSBmb3JtYXQgdXNlZCBieSBrZXlBVE0gDQprZXlzPC1yZWFkX2tleXdvcmRzKGRvY3MgPSBrZXlBVE1fZG9jcywgZGljdGlvbmFyeSA9IGRpY3RfdG9waWMpDQoNCiN2aXN1YWxpemUga2V5d29yZHM6IA0Ka2V5X3ZpejwtdmlzdWFsaXplX2tleXdvcmRzKGRvY3MgPSBrZXlBVE1fZG9jcywga2V5d29yZHMgPSBrZXlzKQ0Ka2V5X3Zpeg0KYGBgDQoNCg0KQW5kIGhlcmUncyB0aGUgbW9kZWwgdHJhaW5pbmcgZnVuY3Rpb24uIFlvdSdsbCBwcm9iYWJseSBub3RpY2UgdGhhdCB0aGlzIGlzIHF1aXRlIGEgYml0IHNsb3dlciB0aGFuIGVpdGhlciB0aGUgc3RhbmRhcmQgb3Igc2VlZGVkIExEQS4gWW91IG1pZ2h0IHdhbnQgdG8gZ2V0IHRoaXMgcnVubmluZyBhbmQgZ28gZ3JhYiBhIHNuYWNrLiBUaGUgYXV0aG9ycyByZWNvbW1lbmQgc2F2aW5nIHRoZSBtb2RlbCBhZnRlciBpdCBpcyBmaW5pc2hlZCBydW5uaW5nLCBzbyBJJ20gZ29pbmcgdG8gZG8gdGhhdCBoZXJlLiBUbyBsb2FkIGl0IGJhY2sgdXAsIHlvdSB3b3VsZCBqdXN0IG5lZWQgdG8gcnVuIGBga2xkYV9tb2RlbCAgPC0gcmVhZFJEUygna2xkYS5yZHMnKSdgLiANCg0KYGBge3J9DQprbGRhX21vZGVsIDwtIGtleUFUTSgNCiAgZG9jcyAgICAgICAgICAgICAgPSBrZXlBVE1fZG9jcywgICAgIyB0ZXh0IGlucHV0DQogIG5vX2tleXdvcmRfdG9waWNzID0gNSwgICAgICAgICAgICAgICMgNSBleHRyYSBnYXJiYWdlIHRvcGljcw0KICBrZXl3b3JkcyAgICAgICAgICA9IGtleXMsICAgICAgICMgb3VyIGxpc3Qgb2YgdGVybXMNCiAgbW9kZWwgICAgICAgICAgICAgPSAiYmFzZSIsICAgICAgICAgIyB0aGUgYmFzZSBtb2RlbA0KICANCiAgb3B0aW9ucyAgICAgICAgICAgPSBsaXN0KHNlZWQgPSAyNTApICMgc2V0IHRoZSByYW5kb20gbnVtYmVyIHNlZWQgaW5zaWRlIHRoZSBmdW5jdGlvbg0KKQ0KDQojIHNhdmUgdGhlIHRyYWluZWQgbW9kZWwNCnNhdmVSRFMoa2xkYV9tb2RlbCwgZmlsZT0na2xkYS5yZHMnKQ0KYGBgDQoNCllvdSBjYW4gZ2V0IHRoZSBoaWdoZXN0IHByb2JhYmlsaXR5IHdvcmRzIHVzaW5nIHRoZSBgYHRvcF93b3Jkc2BgIGZ1bmN0aW9uLiBUaGUgcmVzdWx0aW5nIGRhdGFmcmFtZSB3aWxsIGhhdmUgYSBjaGVja21hcmsgbmV4dCB0byB0ZXJtcyB0aGF0IHdlcmUgcGFydCBvZiB5b3VyIGtleXdvcmQgbGlzdC4gDQoNCmBgYHtyfQ0KdG9wX3dvcmRzKGtsZGFfbW9kZWwpDQpgYGANCg0KDQpPbmNlIGFnYWluLCB3ZSBjYW4gdXNlIHRoaXMgdG8gcmV0cmlldmUgc3BlY2lmaWMgZG9jdW1lbnRzIChhbmQgdG8gY2hlY2sgdGhlIHF1YWxpdHkgb2Ygb3VyIHRvcGljcykNCg0KYGBge3J9DQplbGVjdGlvbl9kZjwtZG9jdmFycyhwb2xfZGZtKSU+JQ0KICAjIGFkZCAlIG9mIGNvdmlkIHRvcGljDQogIG11dGF0ZShlbGVjdGlvbj0ga2xkYV9tb2RlbCR0aGV0YVssJzNfZWxlY3Rpb25zJ10pJT4lDQogIGFycmFuZ2UoLWVsZWN0aW9uKQ0KDQplbGVjdGlvbl9kZiU+JQ0KICBzZWxlY3QodXJsKSU+JQ0KICBzbGljZV9oZWFkKG49MTApDQpgYGANCg0KDQpBbmQgd2UgY2FuIHVzZSB0aGlzIHRvIHRyYWNrIG5ld3MgY292ZXJhZ2Ugb3ZlciB0aW1lLiBIZXJlLCB3ZSBzZWUgcGVha3MgaW4gdGhlICJlbGVjdGlvbnMiIHRvcGljIHRoYXQgcm91Z2hseSBjb2luY2lkZSB3aXRoIHByZXNpZGVudGlhbCBnZW5lcmFsIGVsZWN0aW9uIHNlYXNvbnMgaW4gdGhlIHRpbWUgcGVyaW9kLiANCg0KYGBge3J9DQplbGVjdGlvbl9jb3ZlcmFnZTwtZWxlY3Rpb25fZGYlPiUNCiAgbXV0YXRlKCMgcm91bmQgZG93biBtb250aA0KICAgICAgICAgbW9udGggPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQ9J21vbnRocycpKSU+JQ0KICAjIGdyb3VwIGJ5IG1vbnRoIGFuZCBjb21wdXRlIHRoZSBtb250aGx5IGF2ZXJhZ2U6IA0KICBncm91cF9ieShtb250aCklPiUNCiAgc3VtbWFyaXNlKGVsZWN0aW9uPSBtZWFuKGVsZWN0aW9uKSkNCg0KZWxlY3Rpb25fY292ZXJhZ2UlPiUNCiAgZ2dwbG90KGFlcyh4PW1vbnRoLCB5PWVsZWN0aW9uKSkgKyANCiAgIyBjcmVhdGUgdGhlIGxpbmUgcGxvdA0KICBnZW9tX2xpbmUoKSArDQogIGdlb21fcG9pbnQoKSArDQogIHlsYWIoJ0F2ZXJhZ2UgdG9waWMgJScpICsNCiAgIyB1c2luZyB0aGUgbWluaW1hbCB0aGVtZTogDQogIHRoZW1lX21pbmltYWwoKSArDQogIGdndGl0bGUoJ0F2ZXJhZ2UgJSBlbGVjdGlvbiB0b3BpYyBieSBtb250aCcpIA0KDQpgYGANCg0KIyMjIEFkZGluZyBtZXRhZGF0YQ0KDQpUaGUgcmVhbCBmZWF0dXJlIHRoYXQgZGlzdGluZ3Vpc2hlcyBLZXl3b3JkIGFzc2lzdGVkIHRvcGljIG1vZGVscyBmcm9tIHNlZWRlZCBMREEgaXMgdGhlIGFiaWxpdHkgdG8gaW5jb3Jwb3JhdGUgZG9jdW1lbnQgbWV0YSBkYXRhIHRvIHByZWRpY3QgdG9waWMgcHJldmFsZW5jZSBhIHZlcnkgc2ltaWxhciBmYXNoaW9uIHRvIHRoZSBzdHJ1Y3R1cmFsIHRvcGljIG1vZGVsLiBUaGUgc3ludGF4IGlzIGhlcmUgc2xpZ2h0bHkgZGlmZmVyZW50LCBidXQgdGhlIGJhc2ljIGludHVpdGlvbiBoZXJlIGlzIHRoZSBzYW1lIGFzIGluIFNUTTogd2UgdGhpbmsgdGhhdCBGb3ggTmV3cyBhbmQgQ05OIHdpbGwgdmFyeSBzeXN0ZW1hdGljYWxseSBpbiB0aGUgYW1vdW50IG9mIGF0dGVudGlvbiB0aGV5IGdpdmUgdG8gZGlmZmVyZW50IHN1YmplY3RzLg0KKG5vdGU6IHRoaXMgd2lsbCB0YWtlIGEgd2hpbGUgdG8gY29udmVyZ2UpDQoNCmBgYHtyfQ0Ka2xkYV9jb3ZhcmlhdGUgPC0ga2V5QVRNKA0KICBkb2NzICAgICAgICAgICAgICA9IGtleUFUTV9kb2NzLA0KICBub19rZXl3b3JkX3RvcGljcyA9IDUsDQogIGtleXdvcmRzICAgICAgICAgID0ga2V5cywNCiAgbW9kZWwgICAgICAgICAgICAgPSAiY292YXJpYXRlcyIsDQogIG1vZGVsX3NldHRpbmdzICAgID0gbGlzdChjb3ZhcmlhdGVzX2RhdGEgICAgPSBkb2N2YXJzKHBvbF9kZm0pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgY292YXJpYXRlc19mb3JtdWxhID0gfiBzb3VyY2UpLA0KICBvcHRpb25zICAgICAgICAgICA9IGxpc3Qoc2VlZCA9IDI1MCkNCikNCg0KIyBzYXZlIHRoZSByZXN1bHRzIGFmdGVyIHJ1bm5pbmcgDQpzYXZlUkRTKGtsZGFfY292YXJpYXRlLCBmaWxlPSdrbGRhX2NvdmFyaWF0ZS5yZHMnKQ0KYGBgDQoNCldlIGNhbiB0aGUgdG9wIHdvcmRzIHVzaW5nIHRoZSBzYW1lIG1ldGhvZHMgYXMgYmVmb3JlLiBJdCdzIHBvc3NpYmxlIHRoYXQgdGhlIHJlc3VsdHMgaGVyZSB3aWxsIGhhdmUgaW1wcm92ZSBzb21ld2hhdCBiZWNhdXNlIHRoZSB0b3BpYyBtb2RlbCBpcyBub3cgaW5jb3Jwb3JhdGluZyBkYXRhIG9uIHRoZSBzb3VyY2UgaW50byB0aGUgbWV0YWRhdGEuIEhvd2V2ZXIsIHRoZSBtYWluIGZpbmRpbmcgb2YgaW50ZXJlc3Qgd2lsbCBsaWtlbHkgY29tZSBmcm9tIHZpZXdpbmcgdGhlIGltcGFjdCBvZiB0aGUgY292YXJpYXRlcyBvbiB0aGUgdG9waWMgcHJvYmFiaWxpdGllcy4gV2UgY2FuIHJldHJpZXZlIHRoZXNlIHdpdGggYGJ5X3N0cmF0YV9Eb2NUb3BpYygpYCBhbmQgcGxvdCB0aGVtIHVzaW5nIHRoZSBjb21tYW5kIGJlbG93LiBIZXJlLCB3ZSdyZSBwbG90dGluZyB0aGUgcHJldmFsZW5jZSBmb3IgdGhlIGltbWlncmF0aW9uIHRvcGljLiBUaGUgcmVzdWx0cyBzdWdnZXN0IHRoYXQgRm94IE5ld3MgaXMgc2lnbmlmaWNhbnRseSBtb3JlIGxpa2VseSB0byBjb3ZlciB0aGlzIGlzc3VlIChhdCBsZWFzdCBpbiB0aGlzIHNhbXBsZSkNCg0KYGBge3J9DQoNCiNzb21ldGltZXMgY292YXJpYXRlcyB3aWxsIGJlIHJlbmFtZWQgYXV0b21hdGljYWxseQ0KI3VzZSBjb3ZhcmlhdGVzX2luZm8oa2xkYV9jb3ZhcmlhdGUpIHRvIHNlZSBob3cgeW91ciBjb3ZhcmlhdGVzIG1pZ2h0IGhhdmUgYmVlbiByZW5hbWVkDQpzdHJhdGFfdG9waWMgPC0gYnlfc3RyYXRhX0RvY1RvcGljKA0KICBrbGRhX2NvdmFyaWF0ZSwgDQogICMgInNvdXJjZSIgaGVyZSB3YXMgYXV0b21hdGljYWxseSBjaGFuZ2VkIHRvIHNvdXJjZUZveCBOZXdzDQogIGJ5X3ZhciA9ICJzb3VyY2VGb3ggTmV3cyIsDQogIGxhYmVscyA9IGMoIkNOTiIsICJGb3giKQ0KKQ0KDQpwbG90KHN0cmF0YV90b3BpYywgdmFyX25hbWUgPSAnc291cmNlJywgc2hvd190b3BpYz0gNykNCg0KYGBgDQoNCiMjIEJFUlRvcGljDQoNCkxhc3RseSwgd2hpbGUgcHJvYmFibHkgb3V0c2lkZSB0aGUgc2NvcGUgb2YgdGhpcyBjb3Vyc2UsIEkgZG8gd2FudCB0byBoaWdobGlnaHQgYSBmaW5hbCB2YXJpYXRpb24gb2YgdGhlIHRvcGljIG1vZGVsIGtub3duIGFzIEJFUlRvcGljIFtwYXBlcl0oaHR0cHM6Ly9hcnhpdi5vcmcvcGRmLzIyMDMuMDU3OTQucGRmKS4gUmF0aGVyIHRoYW4gaW5mZXJyaW5nIHRvcGljcyB1c2luZyBhIGRvY3VtZW50LXRlcm0tbWF0cml4LCBCRVJUb3BpYyB1c2VzIGFuIGVtYmVkZGluZyBtb2RlbCAtIGEga2luZCBvZiBudW1lcmljIHJlcHJlc2VudGF0aW9uIG9mIHRleHQgdGhhdCBjYXB0dXJlcyBzZW1hbnRpYyBzaW1pbGFyaXRpZXMgYmV0d2VlbiB3b3Jkcywgc2VudGVuY2VzIG9yIGRvY3VtZW50cyAoZ29vZCBwcmltZXIgb24gdGhpcyBjb25jZXB0IFtoZXJlXShodHRwczovL3d3dy5waW5lY29uZS5pby9sZWFybi9zZXJpZXMvbmxwL2RlbnNlLXZlY3Rvci1lbWJlZGRpbmdzLW5scC8pKS4gSW1wb3J0YW50bHksIHdlIGNhbiB1c2UgcHJlLWV4aXN0aW5nIGVtYmVkZGluZ3MgdGhhdCB3ZXJlIHRyYWluZWQgb24gYSBodWdlIGNvcnB1cyBhbmQgYXBwbHkgdGhlbSB0byBhIG5ldyBzZXQgb2YgZG9jdW1lbnRzLCB3aGljaCBnaXZlcyB1cyBhIG1ham9yIGhlYWQtc3RhcnQgd2hlbiB0cnlpbmcgdG8gdHJhaW4gYSBtb2RlbC4gDQoNCg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCiMgbG9hZCByZXRpY3VsYXRlIA0KbGlicmFyeShyZXRpY3VsYXRlKQ0KIyBydW4gdXNlX3B5dGhvbihwYXRoLXRvLXB5dGhvbiAuZXhlIGZpbGUpIGlmIHlvdSBuZWVkIHRvIHVzZSBhIHNwZWNpZmljIHZlcnNpb24NCg0KYGBgDQoNCg0KQkVSVG9waWMgZ2VuZXJhbGx5IHdvcmtzIGJldHRlciBvbiBwYXJhZ3JhcGhzIG9yIGV2ZW4gc2luZ2xlIHNlbnRlbmNlcywgc28gd2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgc2FtZSBkYXRhc2V0IGhlcmUsIGJ1dCBvbmx5IHRha2UgdGhlIGZpcnN0IDUgc2VudGVuY2VzIGZyb20gZWFjaCBhcnRpY2xlLiBPdGhlciB0aGFuIHRoYXQsIHdlIHJlYWxseSBkb24ndCBuZWVkIHRvIGRvICphbnkqIHByZS1jbGVhbmluZyBvZiB0aGUgZGF0YSBiZWNhdXNlIFt0aGUgc2VudGVuY2UgZW1iZWRkaW5nIG1vZGVsXShodHRwczovL2FyeGl2Lm9yZy9hYnMvMTkwOC4xMDA4NCkgd2UncmUgYXBwbHlpbmcgaGVyZSB3YXMgdHJhaW5lZCBvbiByZWd1bGFyIGh1bWFuIHNlbnRlbmNlcyBhbmQgY2FuIHVzZSB3b3JkIG9yZGVyIGFuZCBjb250ZXh0IGNsdWVzIHdoZW4gcHJvZHVjaW5nIG51bWVyaWMgcmVwcmVzZW50YXRpb25zIG9mIHNlbnRlbmNlcy4gDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQoNCmltcG9ydCBwYW5kYXMgYXMgcGQNCmZyb20gbmx0ay50b2tlbml6ZSBpbXBvcnQgc2VudF90b2tlbml6ZSwgd29yZF90b2tlbml6ZQ0KZnJvbSBiZXJ0b3BpYyBpbXBvcnQgQkVSVG9waWMNCmZyb20gc2VudGVuY2VfdHJhbnNmb3JtZXJzIGltcG9ydCBTZW50ZW5jZVRyYW5zZm9ybWVyDQoNCiMgcmVhZCB0aGUgY29ycHVzDQpwb2xkb2NzID0gcGQucmVhZF9jc3YoJ2h0dHBzOi8vZ2l0aHViLmNvbS9OZWlsYmx1bmQvQVBBTi9yYXcvbWFpbi9uZXdzX3NhbXBsZS5jc3YnKQ0KDQojIHNwbGl0IHRoZSB0ZXh0IGludG8gc2VudGVuY2VzIA0Kc2VudGVuY2VzID0gW3NlbnRfdG9rZW5pemUodGV4dCkgZm9yIHRleHQgaW4gcG9sZG9jcy50ZXh0XQ0KIyBnZXQgdGhlIGZpcnN0IHRocmVlIHNlbnRlbmNlcyBmcm9tIGVhY2ggdGV4dA0Kc2VudGVuY2VzID0gWycgJy5qb2luKHhbOjRdKSBmb3IgeCBpbiBzZW50ZW5jZXNdDQoNCg0KIyBhcHBseSBhIHByZS10cmFpbmVkIGVtYmVkZGluZyBtb2RlbCBvbiB0aGUgc2VudGVuY2VzDQplbWJlZGRpbmdfbW9kZWwgPSBTZW50ZW5jZVRyYW5zZm9ybWVyKCJhbGwtTWluaUxNLUw2LXYyIikNCmVtYmVkZGluZ3MgPSBlbWJlZGRpbmdfbW9kZWwuZW5jb2RlKHNlbnRlbmNlcywgc2hvd19wcm9ncmVzc19iYXI9RmFsc2UpDQoNCg0KdG9waWNfbW9kZWwgPSBCRVJUb3BpYyhlbWJlZGRpbmdfbW9kZWwgPSBlbWJlZGRpbmdfbW9kZWwpDQoNCiMgdHJhaW4gdGhlIG1vZGVsDQp0b3BpY3MsIHByb2JzID0gdG9waWNfbW9kZWwuZml0X3RyYW5zZm9ybShzZW50ZW5jZXMsIGVtYmVkZGluZ3MpDQogDQoNCiMgc2F2ZSB0aGUgcmVzdWx0cyBpbiB0aGUgd29ya2luZyBkaXJlY3RvcnkNCnRvcGljX21vZGVsLnNhdmUoIi4vYmVydG1vZGVsIiwgc2VyaWFsaXphdGlvbj0ic2FmZXRlbnNvcnMiLCBzYXZlX2N0ZmlkZj1UcnVlLCBzYXZlX2VtYmVkZGluZ19tb2RlbD1lbWJlZGRpbmdfbW9kZWwpDQoNCmBgYA0KDQoNClRoZSBiZXJ0b3BpYyBwYWNrYWdlIG9mZmVycyBhIG51bWJlciBvZiBvcHRpb25zIGZvciB2aXN1YWxpemluZyBvdXIgcmVzdWx0cy4gVGhlIGZpcnN0IHBsb3Qgc2hvd3MgYW4gaW50ZXItdG9waWMgZGlzdGFuY2UgbWFwIChzaW1pbGFyIHRvIHRoZSBraW5kIHByb3ZpZGVkIGJ5IExEQVZpcykuIE5vdGFibHk6IHRoZSBCRVJUb3BpYyBtb2RlbCBpbmZlcnJlZCBhIG11Y2ggbGFyZ2VyIG51bWJlciBvZiB0b3BpY3MgKH42MCkgaGVyZSB0aGFuIHdoYXQgd2Ugc2V0IGluIHByZXZpb3VzIGl0ZXJhdGlvbnMsIGFuZCBtYW55IGFwcGVhciB0byByZWZsZWN0IGEgbXVjaCBmaW5lciBsZXZlbCBvZiBkZXRhaWwuIA0KDQoNCmBgYHtweXRob259DQpmcm9tIGJlcnRvcGljIGltcG9ydCBCRVJUb3BpYw0KDQojIGxvYWRpbmcgdGhlIHNhdmVkIG1vZGVsDQpsb2FkZWRfbW9kZWwgPSBCRVJUb3BpYy5sb2FkKCIuL2JlcnRtb2RlbCIpDQoNCiMgdmlzdWFsaXplIHRvcGljIHNpbWlsYXJpdGllczoNCmxvYWRlZF9tb2RlbC52aXN1YWxpemVfdG9waWNzKCkNCg0KbG9hZGVkX21vZGVsLnZpc3VhbGl6ZV9iYXJjaGFydCh0b3BpY3MgPSBbIDEsMywgNCwgMTQsIDE3XSApDQojIGdldHRpbmcgdG9waWMgY291bnRzIGJ5IHNvdXJjZQ0KY2xhc3NlcyA9IHIucG9sZGYuc291cmNlDQp0b3BpY3NfcGVyX2NsYXNzID0gbG9hZGVkX21vZGVsLnRvcGljc19wZXJfY2xhc3Moci5wb2xkZi50ZXh0LCBjbGFzc2VzPWNsYXNzZXMpDQoNCiMgYWJvcnRpb24sIHVrcmFpbmUsIGh1bnRlciBiaWRlbiwgZ2VuZGVyLCBhbmQgamFuIDYNCmxvYWRlZF9tb2RlbC52aXN1YWxpemVfdG9waWNzX3Blcl9jbGFzcyh0b3BpY3NfcGVyX2NsYXNzLCB0b3BpY3MgPSBbIDEsMywgNCwgMTQsIDE3XSwgbm9ybWFsaXplX2ZyZXF1ZW5jeT0gVHJ1ZSkNCg0KDQoNCmBgYA0KDQoNCg0KIyBPdGhlciBzdHVmZiB0byBsb29rIGF0DQoNCk1hbnkgb2YgdGhlc2UgZXhhbXBsZXMgd2VyZSBhZGFwdGVkIGZyb20gdGhlIFF1YW50ZWRhIGRvY3VtZW50YXRpb24uIFRoZSBwYWNrYWdlIGRvY3VtZW50YXRpb24gaXMgW2hlcmVdKGh0dHA6Ly9xdWFudGVkYS5pby8pLCBhbmQgdGhlcmUgaXMgKGFsc28gYSB0dXRvcmlhbCBbaGVyZV0oaHR0cHM6Ly90dXRvcmlhbHMucXVhbnRlZGEuaW8vaW50cm9kdWN0aW9uLykuIE9uZSBpbXBvcnRhbnQgYXJlYSBvZiB0ZXh0IGFuYWx5c2lzIG5vdCBjb3ZlcmVkIGhlcmUgYXJlIGZ1bGx5IHN1cGVydmlzZWQgbW9kZWxzLCB3aGVyZSB5b3UgdGFrZSBhIHNldCBvZiBsYWJlbGVkIGRvY3VtZW50cyBhbmQgdHJhaW4gYSBtb2RlbCB0byBwcmVkaWN0IGEgc2V0IG9mIHVubGFiZWxlZCBkb2N1bWVudHMuIFdoaWxlIHVzZWZ1bCBhbmQgYW4gaW1wb3J0YW50IHBhcnQgb2YgdGhlIHRleHQgYW5hbHlzaXMgZmllbGQgbW9yZSBicm9hZGx5LCBpdCB0aGVzZSBtb2RlbHMgdHlwaWNhbGx5IHJlcXVpcmUgYSBsb3Qgb2YgaGFuZCBjb2RpbmcgYmVmb3JlIHRoZXkgcmVhbGx5IGJlZ2luIHRvIGJlIGVmZmVjdGl2ZS4gVGhhdCBzYWlkLCB0aGUgcXVhbnRlZGEgdHV0b3JpYWwgYWJvdmUgZG9lcyBnbyBpbnRvIHNvbWUgZGV0YWlsIG9uIGhvdyB0byB0cmFpbiBhIGJhc2ljIHRleHQgY2xhc3NpZmllciBpbiBSLiANCg0KSWYgeW91J3JlIHJlYWxseSBpbnRlcmVzdGVkIGluIGdvaW5nIGluIGRlcHRoLCBvbmUgb2YgdGhlIGJlc3Qgc291cmNlcyBvbiB0aGlzIGlzIFtEYW4gSnVyYWZza3ldKGh0dHA6Ly93ZWIuc3RhbmZvcmQuZWR1L35qdXJhZnNreS8pIHdobyBtYWtlcyBzbGlkZXMsIGxlY3R1cmVzLCBhbmQgW2V2ZW4gYW4gZW50aXJlIHRleHRib29rXShodHRwczovL3dlYi5zdGFuZm9yZC5lZHUvfmp1cmFmc2t5L3NscDMvKSBmcmVlbHkgYXZhaWxhYmxlIG9ubGluZS4gDQoNCg==